리알못 React: 1. 컴포넌트 기초

Table of Content

이 글은 Code with Mosh의 Mastering React 과정과 리액트를 다루는 기술(김민준 저) 책을 공부하며 정리한 글입니다. 리알못이라 이상하거나 틀린 내용이 있을 것입니다…

클래스형 컴포넌트 vs 함수형 컴포넌트

클래스형 컴포넌트는 말 그대로 클래스 형태로 구성된 컴포넌트를 말합니다. 클래스형 컴포넌트는 render() 함수가 꼭 있어야 하며, 이 함수는 JSX를 반환해야 합니다.

함수형 컴포넌트도 말 그대로… 함수 형태로 구성된 컴포넌트를 말합니다. 함수형 컴포넌트는 클래스형 컴포넌트보다 선언하기가 편리하며 메모리 자원도 덜 사용합니다. 또한 결과물의 파일 크기도 더 작습니다. 함수형 컴포넌트는 State와 라이프사이클 API 사용이 불가능했지만 React v16.8 이후 Hooks라는 기능이 도입되면서 클래스형 컴포넌트와 비슷하게 사용할 수 있게 되었습니다.

리액트 공식 매뉴얼에서는 함수형 컴포넌트와 Hooks를 사용하여 새로운 컴포넌트를 만들 것을 권장하고 있습니다. 그렇다고 해서 클래스형 컴포넌트가 완전히 사라지는 것은 아니니 클래스형 컴포넌트는 이미 만들어진 리액트 프로젝트의 유지보수 차원에서, 함수형 컴포넌트는 앞으로 만들 리액트 프로젝트를 위해 배워야 할 것 같습니다.

클래스형 컴포넌트 만들기

간단한 문자열을 출력하는 컴포넌트를 만들어보겠습니다. 먼저 React 프로젝트의 src 폴더에 components 폴더를 만든 후 그 안에 MyComponent 라는 이름의 컴포넌트를 만들어보겠습니다.

/***** src/components/myComponent.js *****/

import React, { Component } from 'react';

export default class MyComponent extends Component {
  render() {
    return <div>안녕요?ㅎ</div>;
  }
}

컴포넌트를 생성한 후 App.js에서 컴포넌트를 불러옵니다.

/***** src/App.js *****/

import React, { Component } from 'react';
import MyComponent from './components/myComponent'; // 컴포넌트 import

class App extends Component {
  render() {
    return <MyComponent />; // 컴포넌트 배치
  }
}

export default App;

npm start 명령어를 날려주면 웹 브라우저에 ‘안녕요?ㅎ’ 라는 문자열이 출력됩니다.

함수형 컴포넌트 만들기

React 프로젝트의 src 폴더에 components 폴더를 만든 후 그 안에 MyComponent 라는 이름의 컴포넌트를 만들어보겠습니다.

/***** src/components/myComponent.js *****/

import React from 'react';

const MyComponent = () => {
  return <div>안녕요?ㅎ</div>
};

export default MyComponent;

컴포넌트를 생성한 후 App.js에서 컴포넌트를 불러옵니다.

/***** src/App.js *****/

import React from 'react';
import MyComponent from './components/MyComponent';

const App = () => {
  return (
    <div>
      <MyComponent />
    </div>
  );
};

export default App;

npm start 명령어를 날려주면 웹 브라우저에 ‘안녕요?ㅎ’ 라는 문자열이 출력됩니다.

여러 요소 배치하기

컴포넌트 내에 여러 요소를 배치하려는 경우 하나의 태그(div나 React.Fragment 등)로 여러 요소들을 감싸줘야 합니다.

/***** src/components/myComponent.js *****/

import React, { Component } from 'react';

export default class MyComponent extends Component {
  render() {
    return (
      // h1 태그와 p 태그 배치 후 React.Fragment 태그로 감쌈
      <React.Fragment>
        <h1>Hello World!</h1>
        <p>안녕요?ㅎ</p>
      </React.Fragment>
    );
  }
}
/***** src/components/myComponent.js *****/

import React from 'react';

const MyComponent = () => {
  return (
    // React.Fragment 태그는 이름을 생략할 수 있음
    <>
      <h1>Hello World!</h1>
      <p>안녕요?ㅎ</p>
    </>
  );
};

export default MyComponent;

표현식(Expression)

JavaScript 코드를 입력하려면 표현식 { }을 사용해야 합니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  state = { imageUrl: "https://picsum.photos/200" }; // 랜덤한 200x200 크기의 이미지 

  render() {
    return (
      <div>
        {/* 표현식 { } 내에 JavaScript 코드를 입력 */}
        <img src={this.state.imageUrl} alt="" />
      </div>
    );
  }
}
import React from 'react';

const MyComponent = () => {
  const imageUrl = "https://picsum.photos/200";

  return <img src={imageUrl} alt="" />   // 표현식 { } 내에 JavaScript 코드를 입력
};

export default MyComponent;

스타일 설정

스타일을 지정하려면 styles 객체를 만들어 적용하거나 css 파일을 만들어 import하면 됩니다.

styles 객체 사용

import React, { Component } from 'react';

export default class MyComponent extends Component {
  // 스타일 속성 이름은 카멜 표기법(첫 글자가 대문자)을 따름
  styles = {
    fontSize: 20,
    fontWeight: "bold"
  }

  render() {
    return (
      <div>
        <p style={this.styles}>안녕요?ㅎ</p>
        <p style={{ fontSize: 40, fontWeight: "bold" }}>이런 식으로 스타일을 직접 명시할 수도 있습니다.</p>
      </div>
    );
  }
}
import React from 'react';

const MyComponent = () => {
  const styles = {
    fontSize: 20,
    fontWeight: "bold"
  };

  return (
    <div>
      <p style={styles}>안녕요?ㅎ</p>
      <p style={{ fontSize: 40, fontWeight: "bold"}}>이런 식으로 스타일을 직접 명시할 수 있습니다.</p>
    </div>
  );
};

export default MyComponent;

css 파일 사용

React 프로젝트 src 폴더의 index.css 파일에 다음과 같이 임의로 속성을 추가해줍니다. index.css 대신 css 파일을 별도로 만들어 사용하려면 만든 css 파일을 import 해야 합니다.

/***** index.css *****/

.mycss {
  font-size: 20px;
  font-weight: "bold";
}

태그에 className을 지정해주면 css 스타일이 적용됩니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  render() {
    // className 지정
    return (<div className="mycss">안녕요?ㅎ</div>);
  }
}
import React from 'react';

const MyComponent = () => {
  return <div className="mycss">안녕요?ㅎ</div>
};

export default MyComponent;

배열 렌더링

아래 예제는 Array.map() 함수를 사용하여 문자 배열을 리스트로 출력하는 예제입니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  state = { hello: ["안녕요?ㅎ", "Hello", "니하오"] }

  render() {
    return (
      <ul>
        {/* Array.map() 함수 사용 시 li 태그 내에 key 속성을 주지 않으면 
          * Each child in a list should have a unique "key" prop. 경고가 뜨게 됨 */ }
        {this.state.hello.map(item =>
          <li key={item}>{item}</li>)} 
      </ul>
    );
  }
}
import React from 'react';

const MyComponent = () => {
  const hello = ["안녕요?ㅎ", "Hello?", "니하오"];

  return (
    <ul>
      {hello.map(item => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
};

export default MyComponent;

이벤트 처리

이벤트란 어떤 사건이나 동작이 일어난 순간을 의미합니다. 이벤트는 자체적으로 처리되거나 자식 컴포넌트에서 부모 컴포넌트로 전달된 후 처리됩니다. 여기선 일단 전자만 다뤄봅니다.

이벤트 처리기를 화살표 함수로 구현하면 bind() 메소드를 따로 호출하지 않아도 됩니다. 그렇지 않으면 this.notArrowfunction.bind(this)와 같이 bind() 함수를 호출해줘야 합니다.

아래 예제는 버튼 클릭 시 onClick 이벤트가 발생한 후 handleClick 이벤트 처리기에 의해 경고창을 띄우는 예제입니다. 이벤트 처리기에 전달할 매개변수가 없는 경우 이벤트 처리기 이름만 명시해주면 됩니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  handleClick = () => alert("안녕요?ㅎ"); // 이벤트 처리기

  render() {
    return <button onClick={this.handleClick}>Click!</button> // onClick 이벤트 처리기 명시
  }
}
import React from 'react';

const MyComponent = () => {
  const handleClick = () => alert("안녕요?ㅎ"); // 이벤트 처리기

  return <button onClick={handleClick}>Click!</button> // onClick 이벤트 처리기 명시
};

export default MyComponent;

이벤트 처리기에 전달할 매개변수가 있는 경우 화살표 함수를 통하여 이벤트 처리기와 매개변수를 명시해주면 됩니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  handleClick = (str) => alert(str); // 이벤트 처리기

  render() {
    const str = '안녕요?ㅎ';
    return <button onClick={() => this.handleClick(str)}>Click!</button> // onClick 이벤트 처리기 명시
  }
}
import React from 'react';

const MyComponent = () => {
  const handleClick = (message) => alert(message);

  const message = "안녕요?ㅎ";
  return <button onClick={() => handleClick(message)}>Click!</button>
};

export default MyComponent;

state 사용하기

state는 컴포넌트 내부에서 바뀔 수 있는 값을 뜻합니다. 클래스형 컴포넌트에서는 직접 state 값을 지정하고, 함수형 컴포넌트에서는 Hooks에서 제공하는 useState() 함수를 사용하여 선언합니다.

import React, { Component } from 'react'

export default class MyComponent extends Component {
  state = {
    hello: "안녕요?ㅎ"
  }

  render() {
    return this.state.hello;
  }
}
import React, { useState } from 'react';

const MyComponent = () => {
  // useState() 함수를 호출하면 배열이 반환됨
  // useState() 함수 인자에 상태의 초깃값을 넣어줌(값의 형태는 자유)
  // 첫번째 원소: state, 두번째 원소: state 값을 설정할 세터(Setter) 함수
  const [hello, setHello] = useState({
    korean: "안녕요?ㅎ"
  });

  return hello.korean;
};

export default MyComponent;

state 값 변경

아래 예제는 버튼 클릭시 count 값이 1씩 올라가며 그 값을 출력하는 예제입니다.

클래스형 컴포넌트에서는 setState() 함수를 사용하여 state 값을 변경합니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  state = { count: 0 }

  // setState() 함수를 사용하여 state 값 변경
  handleIncrement = () => { this.setState({ count: this.state.count + 1 }); }

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.handleIncrement}>Increment</button>
      </div>
    );
  }
}

함수형 컴포넌트에서는 Setter 함수를 사용하여 state 값을 변경합니다.

import React, { useState } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0); // setCount()는 Setter 함수
  const handleIncrement = () => setCount(count + 1);

  return (
    <div>
      <p>{count}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
};

export default MyComponent;

bootstrap 사용하기

bootstrap은 각종 레이아웃, 버튼, 입력창 등의 디자인을 구현하기 위한 프레임워크입니다. React에서 className을 명시해주면 bootstrap에서 구현한 디자인을 적용할 수 있습니다.

bootstrap 설치

터미널에서 React 프로젝트 경로로 이동한 후 npm i bootstrap 명령어를 실행하면 bootstrap이 설치됩니다.

bootstrap.css import

React 프로젝트 루트 디렉토리에 있는 index.js 파일에 bootstrap.css를 import하는 코드를 추가해줍니다.

/***** index.js *****/

import 'bootstrap/dist/css/bootstrap.css';

bootstrap 사용하기

bootstrap 디자인을 적용할 태그에 className을 명시해주면 됩니다. 어떻게 명시해야 되는지는 https://www.w3schools.com/bootstrap/bootstrap_ver.asp 를 참고합니다.

아래는 여러 개의 버튼에 대충 bootstrap 디자인을 적용해본 예제입니다.

import React, { Component } from 'react';

export default class MyComponent extends Component {
  render() {
    return (
      <div>
        <button className="btn btn-primary btn-lg">Primary</button>
        <button className="btn btn-danger">Danger</button>
        <button className="btn btn-primary btn-sm">
          <span className="spinner-border spinner-border-sm"></span>
          Loading..
        </button>
      </div>
    );
  }
}
import React from "react";

const MyComponent = () => {
  return (
    <div>
      <button className="btn btn-primary btn-lg">Primary</button>
      <button className="btn btn-danger">Danger</button>
      <button className="btn btn-primary btn-sm">
        <span className="spinner-border spinner-border-sm"></span>
        Loading..
      </button>
    </div>
  );
};

export default MyComponent;

연습: 영화 목록 보고 지우기

영화 정보를 대충 하드코딩으로 때려 박고 그 정보를 테이블 형태로 출력한 후 원하는 영화 정보를 삭제할 수 있도록 만들어 본 예제입니다.

Bootstrap을 사용하면 조금 더 깔끔하게 보여주는데… npm i bootstrap@4.1.1 명령어로 React 프로젝트에 Bootstrap을 설치한 후 index.js 파일에 import 'bootstrap/dist/css/bootstrap.css';를 추가해줍니다.

Font Awesome 이라는 것도 npm i font-awesome@4.7.0 명령어로 React 프로젝트에 Font Awesome을 설치한 후 index.js 파일에 import 'font-awesome/css/font-awesome.css';를 추가해줍니다.

import React, { Component } from 'react';

export default class Movies extends Component {
  state = {
    movies: [
      { id: 0, title: "기생충", release: "2019-05-30" },
      { id: 1, title: "라이온 킹", release: "2019-07-17" },
      { id: 2, title: "알라딘", release: "2019-05-23" },
      { id: 3, title: "나랏말싸미", release: "2019-07-24" },
      { id: 4, title: "주전장", release: "2019-07-25" },
      { id: 5, title: "어벤져스: 엔드게임", release: "2019-04-24" }
    ]
  }

  // 이벤트 처리기: 버튼 클릭 시 해당 영화 목록 삭제
  handleDelete = (movie) => {
    const movies = this.state.movies.filter(m => m.id !== movie.id);
    this.setState({ movies }); // movies: = movies 코드의 축약형. Modern JavaScript에서 키 값과 value가 서로 같으면 이렇게 표현 가능.
  }

  render() {
    const { length: count } = this.state.movies;

    if (count === 0)
      return <p>영화 정보가 없습니다.</p>

    return (
      <React.Fragment>
        <p>{count} 개의 영화 정보가 있습니다.</p>

        {/* VS Code에서 table.table>thead>tr>th*4를 입력하면 아래와 같이 table 태그를 쉽게 만들 수 있음. (th*4: th 태그를 4개 생성) */}
        <table className="table">
          <thead>
            <tr>
              <th>ID</th>
              <th>제목</th>
              <th>출시일</th>
              <th>삭제</th>
            </tr>
          </thead>
          <tbody>
            {this.state.movies.map(movie =>
              <tr key={movie.id}>
                <td>{movie.id}</td>
                <td>{movie.title}</td>
                <td>{movie.release}</td>
                <td><button onClick={() => this.handleDelete(movie)}>Delete</button></td>
              </tr>
            )}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}
import React, { useState } from "react";

const Movies = () => {
  const [movies, setMovies] = useState([
    { id: 0, title: "기생충", release: "2019-05-30" },
    { id: 1, title: "라이온 킹", release: "2019-07-17" },
    { id: 2, title: "알라딘", release: "2019-05-23" },
    { id: 3, title: "나랏말싸미", release: "2019-07-24" },
    { id: 4, title: "주전장", release: "2019-07-25" },
    { id: 5, title: "어벤져스: 엔드게임", release: "2019-04-24" },
  ]);

  // 이벤트 처리기: 버튼 클릭 시 해당 영화 목록 삭제
  const handleDelete = (movie) => {
    const newMovies = movies.filter(m => m.id !== movie.id);
    setMovies(newMovies);
  }

  const count = movies.length;
  if(count === 0)
    return <p>영화 정보가 없습니다.</p>

  return (
    <>
      <p>{count}개의 영화 정보가 있습니다.</p>
      <table className="table">
        <thead>
          <tr>
            <th>ID</th>
            <th>제목</th>
            <th>출시일</th>
            <th>삭제</th>
          </tr>
        </thead>
        <tbody>
          {movies.map(movie => (
            <tr key={movie.id}>
              <td>{movie.id}</td>
              <td>{movie.title}</td>
              <td>{movie.release}</td>
              <td><button onClick={() => handleDelete(movie)}>삭제</button></td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
};

export default Movies;

댓글 남기기