본문 바로가기
리액트를 다루는 기술

[리액트를 다루는 기술] 3장 컴포넌트

by 도리에몽 2022. 3. 9.

 컴포넌트는 데이터가 주어졌을 때 이에 맞춰 UI를 만들어 준다. 또한, 라이프사이클 API를 이용하여 컴포넌트가 화면에서 나타날 때, 사라질 때, 변화가 일어날 때 주어진 작업들을 처리할 수 있으며, 임의 메서드를 만들어 특별한 기능을 붙여줄 수 있다.


3.1 클래스형 컴포넌트

컴포넌트 선언 방식은 함수형 컴포넌트와 클래스형 컴포넌트로 2가지이다.

import React, { Component } from 'react';

class App extends Component {
  render() {
    const name = 'react';
    return <div className="react">{name}</div>;
  }
}
export default App;

클래스형 컴포넌트의 경우, state 기능 및 라이프사이클 기능을 사용할 수 있다는 점과 임의 메서드를 정의할 수 있다는 점에서 함수형 컴포넌트와 차이점이 있다. 클래스형 컴포넌트에서는 render 함수가 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 한다.

 

- 함수형 컴포넌트의 장점

1. 클래스형 컴포넌트 보다 선언하기 쉬움

2. 메모리 자원을 덜 사용함.

3. 프로젝트 완성 후 빌드하여 배포할 때 결과물의 파일 크기가 더 작음.

 

- 함수형 컴포넌트의 단점

state와 라이프사이클 API 사용이 불가능함. (BUT Hooks 기능이 도입되면서 해결됨)

 

3.2 첫 컴포넌트 생성

1. src 디렉터리에 MyComponent.js 파일 생성(컴포넌트 코드를 선언할 파일 생성)

2. 코드 작성

import React from 'react';
const MyComponent = () => {
  return <div>나의 새롭고 멋진 컴포넌트</div>;
}; 
export default MyComponent;

function 키워드 대신에 () => { } (*화살표 함수)를 사용하여 함수를 만든 예시.

 

*화살표 함수

주로 함수를 파라미터로 전달할 때 유용함. 일반 함수는 자신이 종속된 객체를 this로 가리키며, 화살표 함수는 자신이 종속된 인스턴스를 가리킨다. 화살표 함수는 값을 연산하여 바로 반환해야 할 때 사용하면 가독성↑

function twice(value) {
  return value * 2;
}
const triple = (value) => value * 3;

 

3. 모듈 내보내기 및 불러오기

3-1 모듈 내보내기(export)

//MyComponent.js
export default MyComponent;

다음 코드는 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정한다.

 

3-2 모듈 불러오기(import)

App 컴포넌트에서 MyComponent 컴포넌트를 불러와서 사용해 보자.

import React from 'react';
import MyComponent from './MyComponent';
 
const App = () => {
  return <MyComponent />;
};
 
export default App;

 

3.3 props

props는 properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소이다. props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.

 

1. JSX 내부에서 props 렌더링

MyComponent에서 name이라는 props를 렌더링하도록 설정해 보자. props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있다. props를 렌더링할 때는 JSX 내부에서 { } 기호로 감싸 준다.

//MyComponent.js
const MyComponent = props => {
  return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};

 

2. 컴포넌트를 사용할 때 props 값 지정하기

App 컴포넌트에서 MyComponent의 props 값을 지정해 보자.

//App.js
import React from 'react';
import MyComponent from './MyComponent';
 
const App = () => {
  return <MyComponent name="React" />;
};
 
export default App;

 

3. props 기본값 설정: defaultProps

props 값을 따로 지정하지 않았을 때 보여 줄 기본값을 설정해 보자.

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

const MyComponent = props => {
  return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};

MyComponent.defaultProps = {
  name: '기본 이름'
};
 
export default MyComponent;

 

4. 태그 사이의 내용을 보여 주는 children

children은 컴포넌트 태그 사이의 내용을 보여 주는 props이다.

//App.js
import React from 'react';
import MyComponent from './MyComponent';
 
const App = () => {
  return <MyComponent>리액트</MyComponent>;
};
 
export default App;

위 코드에서 MyComponent 태그 사이의 '리액트' 문자열을 MyComponent 컴포넌트 내부에서 보여 주려면 props.children 값을 보여 주어야 한다.

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

const MyComponent = props => {
  return 
    <div>
      안녕하세요, 제 이름은 {props.name}입니다. <br />
      children 값은 {props.children} 입니다.
    </div>;
};

MyComponent.defaultProps = {
  name: '기본 이름'
};
 
export default MyComponent;

 

5. 비구조화 할당 문법을 통해 props 내부 값 추출하기

MyComponent에서 props 값을 조회할 때마다 props.name, props.children과 같이 props.을 앞에 붙여 주고 있다. *비구조화 할당 문법을 사용하면 내부 값을 바로 추출할 수 있어 작업을 더 편리하게 할 수 있다.

//MyComponent.js
(...)
const MyComponent = props => {
  const { name, children } = props;
  return 
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children} 입니다.
    </div>;
};
(...)

*비구조화 할당 문법(구조 분해 문법)

객체에서 값을 추출하는 문법. 함수의 파라미터 부분에서도 사용할 수 있다.

 

- 파라미터 부분에 비구조화 할당 문법을 사용한 경우

//MyComponent.js
(...)
const MyComponent = ({ name, children }) => {
  const { name, children } = props;
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children} 입니다.
    </div>;
    );
};
(...)

 

6. propTypes를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 propTypes를 사용한다. propTypes를 사용하려면 코드 상단에 import 구문을 사용하여 불러와야 한다.

//MyComponent.js
import React from 'react';
import PropTypes from 'prop-types';

(...)
MyComponent.propTypes = {
  name: PropTypes.string  //name 값은 무조건 문자열 형태로 전달해야 함
};

export default MyComponent;

 

6-1 isRequired를 사용하여 필수 propTypes 설정

propTypes를 설정하지 않았을 때 경고 메시지를 띄워 주는 작업을 해 보자. 현재는 favoriteNumber라는 숫자를 필수 props로 지정해 보자.

//MyComponent.js
import React from 'react';
import PropTypes from 'prop-types';

const MyComponent = ({ name, favoriteNumber, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
      <br />
      제가 좋아하는 숫자는 {favoriteNumber}입니다.
    </div>
  );
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired
};

export default MyComponent;
//App.js
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
  return (
    <MyComponent name="React" favoriteNumber={1}>
      리액트
    </MyComponent>
  );
};

export default App;

 

7. 클래스형 컴포넌트에서 props 사용하기

클래스형 컴포넌트에서 props를 사용할 때는 render 함수에서 this.props를 조회하면 된다. defaultProps와 propTypes는 똑같은 방식으로 설정 가능하다.

//MyComponent.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class MyComponent extends Component {
  render() {
    const { name, favoriteNumber, children } = this.props; // 비구조화 할당
      return (
        <div>
          안녕하세요, 제 이름은 {name}입니다. <br />
          children 값은 {children}
          입니다.
          <br />
          제가 좋아하는 숫자는 {favoriteNumber}입니다.
        </div>
      );
  }
}
(...)

defaultProps와 propTypes를 class 내부에서 지정하는 방법도 있다.

//MyComponent.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class MyComponent extends Component {
  static defaultProps = {
    name: '기본 이름'
  };
  static propTypes = {
    name: PropTypes.string,
    favoriteNumber: PropTypes.number.isRequired
  };
(...)

 

3.4 state

state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다. props는 부모 컴포넌트에서 바꿀 수 있다. 리액트에는 1. 클래스형 컴포넌트가 지니고 있는 state 2. 함수형 컴포넌트에서 useState 함수를 통해 사용하는 state 이렇게 두 가지 종류의 state가 있다.

 

1. 클래스형 컴포넌트의 state

//Counter.js
import React, { Component } from ‘react‘;
 
class Counter extends Component {
  {/* 컴포넌트에 state를 설정할 때는 constructor 메서드를 작성하여 설정함. 이는 컴포넌트의 생성자 메서드.
  클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 함.
  */}
  constructor(props) {
    super(props); //이 함수 호출 시 클래스형 컴포넌트가 상속하고 있는 Component 클래스가 지닌 생성자 함수 호출함.
    // state의 초깃값 설정하기
    this.state = {
      number: 0
    };
  }
  render() {
    const { number } = this.state; // state를 조회할 때는 this.state로 조회합니다.
    return (
      <div>
        <h1>{number}</h1>
        {/* 이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용하여 넣어 줌
        onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
        */}
        <button         
          onClick={() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}
 
export default Counter;

 1-1 state 객체 안에 여러 값이 있을 때

this.setState 함수는 인자로 전달된 객체 안에 들어 있는 값만 바꾸어 준다.

 

1-2 state를 constructor에서 꺼내기

constructor 메서드를 선언하지 않고도 다른 방법으로 state의 초기값을 지정해 줄 수 있다.

//Counter.js
import React, { Component } from ‘react‘;
 
class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0
  };
  (...)
}
 export default Counter;

 

1-3 this.setState에 객체 대신 함수 인자 전달하기

this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트된다. 이에 대한 해결책은 this.setState를 사용할 때 객체 대신에 함수를 인자로 넣어 주는 것이다. 인자로 함수로 넣어 줄 때는 다음과 같이 작성한다.

this.setState((prevState, props0 => {
  return {
    //업데이트하고 싶은 내용
  }
})
//prevState는 기존 상태를, props는 현재 지니고 있는 props를 나타냄
//업데이트 과정에서 props가 필요하지 않다면 생략 가능
//Counter.js - button
<button
  // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
  onClick={() => {
    this.setState(prevState => {
      return {
        number: prevState.number + 1
      };
    });
    // 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
    // 아래 코드는 함수에서 바로 객체를 반환한다는 의미입니다.
    this.setState(prevState => ({
      number: prevState.number + 1
    }));
  }}
>
  +1
</button>

화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 { }를 생략하면 된다. onClick에서 두 번쨰로 this.setState 함수를 사용할 때는 화살표 함수에서 바로 객체를 반환하도록 했기 때문에 prevState => ({ })와 같은 형태의 코드로 이루어진다.

 

1-4 this.setState가 끝난 후 특정 작업 실행하기

setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때, setState의 두 번째 파라미터로 콜백 함수를 등록하여 처리할 수 있다.

//Counter.js - button
<button
  // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
  onClick={() => {
    this.setState(
      {
        number: number + 1
      },
      () => {
        console.log(‘방금 setState가 호출되었습니다.’);
        console.log(this.state);
      }
    );
  }}
>
  +1
</button>

 

2. 함수형 컴포넌트에서 useState 사용하기

2-1 배열 비구조화 할당

배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해 주는 문법.

const array = [1, 2];
const one = array[0];
const two = array[1];

//배열 비구조화 할당 사용 시
const array = [1, 2];
const [one, two] = array;

 

2-2 useState 사용하기

useState  함수의 인자에는 상태의 초기값을 넣어 준다(값의 형태는 자유). 함수를 호출하면 배열이 반환되는데 배열의 첫 번째 원소는 현재 상태, 두 번째 원소는 상태를 바꾸어 주는 함수(세터 함수)이다. 배열 비구조화 할당을 통해 자유롭게 이름 지정 가능.

//Say.js
import React, { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage(‘안녕하세요!’);
  const onClickLeave = () => setMessage(‘안녕히 가세요!’);

return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1>{message}</h1>
    </div>
  );
};

export default Say;

 

2-3 한 컴포넌트에서 useState 여러 번 사용하기

useState는 한 컴포넌트에서 여러 번 사용해도 된다.

//Say.js
import React, { useState } from ‘react‘;
(...)
const [color, setColor] = useState(‘black‘);

return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1 style={{ color }}>{message}</h1>
      <button style={{ color: 'red' }} onClick={() => setColor('red')}>
        빨간색
      </button>
      <button style={{ color: 'green' }} onClick={() => setColor('green')}>
        초록색
      </button>
      <button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
        파란색
      </button>
    </div>
  );
};

export default Say;

 

3.5 state를 사용할 때 주의 사항

state 값을 바꾸어야 할 때는 setState 또는 useState를 통해 전달받은 세터 함수를 사용해야 한다. 배열이나 객체를 업데이트해야 할 때는 배열이나 객체 사본을 만들고 그 사본에 값을 업데이트한 후, 사본의 상태를 setState혹은 세터 함수를 통해 업데이트한다.

//사본을 만들어서 업데이트하는 예시
// 객체 다루기
const object = { a: 1, b: 2, c: 3 };
const nextObject = { ...object, b: 2 }; // 사본을 만들어서 b 값만 덮어 쓰기
 
// 배열 다루기
const array = [
{ id: 1, value: true },
{ id: 2, value: true },
{ id: 3, value: false }
];
let nextArray = array.concat({ id: 4 }); // 새 항목 추가
nextArray.filter(item => item.id != = 2); // id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? { ...item, value: false } : item)); // id가 1인 항목의 value를 false로 설정

객체에 대한 사본을 만들 때는 spread 연산자라 불리는 ...을 사용하고, 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 사용한다.