상세 컨텐츠

본문 제목

[리액트 스타터 3] 3장. 컴포넌트

22-23/22-23 리액트 스타터 3

by 케이비이 2022. 10. 6. 10:00

본문

728x90

3.1 클래스형 컴포넌트

 컴포넌트를 선언하는 방식은 두 가지로, 함수 컴포넌트 클래스형 컴포넌트가 있습니다.

2장에서 보았던 코드는 함수 컴포넌트로, 다음과 같은 구조로 이루어져 있습니다.

import './App.css';

function App() {
    const name = 'react';
    return <div className="react">{name}</div>;
}

export default App;

함수 컴포넌트는 클래스형 컴포넌트보다 선언하기 훨씬 편하다는 장점이 있습니다.

 

 다음은 클래스형 컴포넌트의 구조를 보겠습니다.

import React, { Component } from 'react';

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

export default App;

클래스형 컴포넌트에서는 render 함수가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 합니다. 클래스형 컴포넌트로 바뀌었지만 역할은 이전에 보았던 함수 컴포넌트와 똑같습니다. 클래스형 컴포넌트와 함수 컴포넌트의 차이점은 클래스형 컴포넌트의 경우 state 기능  라이프사이클 기능을 사용할 수 있다는 것과 임의 메서드를 정의할 수 있다는 것입니다.

 이처럼 함수 컴포넌트의 주요 단점으로는 state와 라이프사이클 API의 사용이 불가능하다는 점인데, 이 단점은 리액트 v16.8 업데이트 이후 Hooks라는 기능이 도입되면서 해결되었습니다. 완전히 클래스형 컴포넌트처럼 사용할 수는 없지만 비슷하게 작업이 가능합니다. Hooks에 대한 내용은 8장에서 더 자세히 다루게 됩니다. 참고로, 리액트 공식 매뉴얼에서는 컴포넌트를 작성할 때 함수 컴포넌트 Hooks를 사용하도록 권장하고 있습니다.

 

 

3.2 첫 컴포넌트 생성

 컴포넌트를 만들려면 컴포넌트 코드를 선언할 파일(.js)을 만들고, 새 컴포넌트의 코드를 작성하면 됩니다.

 

참고) VS Code에서 Reactjs Code Snippet 확장 프로그램을 설치했다면 컴포넌트 코드를 간편하고 빠르게 생성할 수 있습니다. rsc를 입력하고 Enter키를 누르면 자동으로 함수 컴포넌트 코드가 생성되며, 클래스형 컴포넌트는 rcc를 입력하여 사용할 수 있습니다.

 

작성한 컴포넌트의 맨 아래 줄에는 export default MyComponent(컴포넌트 이름);라는 코드가 적혀있습니다. 이 코드는 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정하며, 이렇게 export를 사용하여 모듈 내보내기가 가능합니다. 다음은 import를 이용한 모듈 불러오기입니다. import MyComponent from './MyComponent'; 코드를 통해 우리가 만든 MyComponent 컴포넌트를 불러옵니다.

 

 

3.3 props

 props는 properties의 줄임말로 컴포넌트 속성을 설정할 때 사용하는 요소입니다. props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있으며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있습니다.

 

3.3.1 JSX 내부에서 props 렌더링

 props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있습니다. props를 렌더링할 때는 JSX 내부에서 { } 기호로 감싸 주면 됩니다. 다음은 name이라는 props를 렌더링하도록 설정한 코드입니다.

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

export default MyComponent;

 

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

 App 컴포넌트에서 MyComponent의 props 값을 지정해보겠습니다.

import MyComponent from "./MyComponent";

const App = () => {
  return <MyComponent name="React" />;
};

export default App;

위 코드들을 통해 브라우저에는 안녕하세요, 제 이름은 React입니다. 라고 적힌 결과가 나타나게 됩니다.

 

3.3.3 props 기본값 설정: defaultProps

 방금 설정한 name 값을 지워서 props 값을 따로 지정하지 않게 된다면, defaultProps를 통해 기본값을 설정할 수 있습니다.

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

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

export default MyComponent;

결과: 안녕하세요, 제 이름은 기본 이름입니다.

 

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

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

import MyComponent from "./MyComponent";

const App = () => {
  return <MyComponent>리액트</MyComponent>;
};

export default App;

위와 같이 컴포넌트 태그 사이에 작성한 '리액트'라는 문자열을 MyComponent 내부에서 보여 주려면 다음과 같이 props.children 값을 보여 주어야 합니다.

const MyComponent = props => {
  return (
    <div>
      children 값은 {props.children}입니다.
    </div>
  );
};

export default MyComponent;

결과: children 값은 리액트입니다.

 

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

 현재 컴포넌트에서 props 값을 조회할 때마다 props.name, props.children과 같이 props.이라는 키워드를 앞에 붙여 주고 있는데, ES6의 비구조화 할당 문법을 사용하면 내부 값을 바로 추출할 수 있게 됩니다.

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

(...)

위와 같이 const { name, children } = props; 코드를 통해 name 값과 children 값을 더 짧은 코드로 사용할 수 있습니다. 이처럼 객체에서 값을 추출하는 문법을 비구조화 할당(destructuring assignment)이라고 부릅니다. 구조 분해 문법이라고도 불리며, 다음과 같이 함수의 파라미터 부분에서도 사용할 수 있습니다.

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

(...)

 

3.3.6 propTypes를 통한 props 검증

 컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 propTypes를 사용합니다. 코드 상단에 import 구문을 사용해 불러와 propTypes를 사용할 수 있습니다.

import PropTypes from 'prop-types';

(...)

MyComponent.propTypes = {
  name: PropTypes.string
};

export default MyComponent;

위와 같이 PropTypes를 불러오고 코드를 작성해 주면 name 값은 무조건 문자열(string) 형태로 전달해야 된다는 것을 의미합니다.

참고) propTypes를 지정할 때 뒤에 .isRequired를 붙여 주면, propTypes를 지정하지 않았을 때 경고 메시지를 띄워 주게 됩니다.

 

- 더 많은 PropTypes 종류

array: 배열

arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열을 의미. ex) arrayOf(PropTypes.number): 숫자로 이루어진 배열

bool: true 혹은 false 값

func: 함수

number: 숫자

object: 객체

string: 문자열

symbol: ES6의 Symbol

node: 렌더링할 수 있는 모든 것(숫자, 문자열, 혹은 JSX 코드)

instanceOf(클래스): 특정 클래스의 인스턴스

oneOf(['dog', 'cat']): 주어진 배열 요소 중 값 하나

oneOfType([React.PropTypes.string, PropTypes.number]): 주어진 배열 안의 종류 중 하나

objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 PropType인 객체

shape({ name: PropTypes.string, num: PropTypes.number }): 주어진 스키마를 가진 객체

any: 아무 종류

더 자세한 정보는 https://github.com/facebook/prop-types에서 확인할 수 있습니다.

 

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

 클래스형 컴포넌트에서 props를 사용할 때는 render 함수에서 다음과 같이 this.props를 조회하면 됩니다.

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>
    );
  }
}

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

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

export default MyComponent;

 또한 다음과 같이 클래스형 컴포넌트에서 defaultProps와 propTypes를 설정할 때 class 내부에서 지정하는 방법도 있습니다.

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
  }
  render() {
    const { name, favoriteNumber, children } = this.props;
    return (...);
  }
}

export default MyComponent;

 

 

3.4 state

 state 컴포넌트 내부에서 바뀔 수 있는 값을 의미합니다. 리액트에는 두 가지 종류의 state가 있습니다.

  • 클래스형 컴포넌트가 지니고 있는 state
  • 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state

 

3.4.1 클래스형 컴포넌트의 state

 컴포넌트에 state를 설정할 때는 다음과 같이 컴포넌트의 생성자 메서드 constructor를 작성하여 설정합니다.

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 }; //state의 초깃값 설정하기
  }
  
(...)

클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 합니다. 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해 줍니다. 컴포넌트의 state는 객체 형식이어야 하며, this.state 값에 초깃값을 설정해 주었습니다. 

 위 코드에 이어지는 render 함수를 확인해 봅시다.

  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button onClick={()=>{this.setState({number: number+1});}}>
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 됩니다. button 안에 onClick이라는 값을 props로 넣어 주었으며, 이는 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 해 줍니다. 이러한 이벤트 시스템은 4장에서 자세히 다룹니다. 이벤트로 설정할 함수를 넣을 때에는 화살표 함수 문법을 사용해야 합니다. 위 코드에서 함수 내부에서는 this.setState라는 함수를 사용했는데, 이는 state 값을 바꿀 수 있게 해 줍니다.

 위 코드 작성 후 다음과 같이 Counter 컴포넌트를 App에서 불러와 렌더링하면,

import Counter from "./Counter";

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

export default App;

브라우저에 다음과 같은 결과가 나오게 됩니다. 버튼을 누르면 숫자가 1씩 올라갑니다.

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

 state 객체 안에는 여러 값이 있을 수 있습니다. 위 코드를 수정하여 다음과 같이 작성할 수 있습니다.

import React, { Component } from 'react';

class Counter extends Component {
   constructor (props) {
      super(props);
      this.state = {
          number: 0,
          fiexdNumber: 0
        };
      }
      render() {
        const { number, fixedNumber } = this.state;
        return (
          <div>
            <h1>{number}</h1>
            <h2>바뀌지 않는 값: {fixedNumber}</h2>
        
(...)

 

3.4.1.2 state를 constructor에서 꺼내기

 앞에서 constructor 메서드 선언을 통해 state 초깃값을 지정하였는데, 다음과 같은 방식으로 constructor 메서드 선언 없이 state 초깃값을 설정할 수 있습니다.

import React, { Component } from 'react';

class Counter extends Component {
  state = {
      number: 0,
      fiexdNumber: 0
  };
  
(...)

 

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

 this.setState의 인자 함수를 넣어 줄 때는 다음과 같은 형식으로 코드를 작성합니다.

this.setState((prevState, props) => {
	return {
    	// 업데이트하고 싶은 내용
    }
})

여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리킵니다.(props 생략 가능)

 이와 같은 방법으로 위 코드를 수정해 봅시다.

(...)

	<button onClick={()=>{
            this.setState(prevState => ({
              number: prevState.number+1
            }));
          }}
        >
          +1
        </button>
        
(...)

 

3.4.1.4 this.setState가 끝난 후 특정 작업 실행하기

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

(...)

	<button onClick={()=>{
            this.setState(
              { number: number+1 },
              () => { console.log('방금 setState가 호출되었습니다.');
                      console.log(this.state); }
            );
          }}
        >
          +1
        </button>
        
(...)

 

3.4.2 함수 컴포넌트에서 useState 사용하기

 리액트 16.8 이전 버전에서는 함수 컴포넌트에서 state를 사용할 수 없었지만, 이후 버전부터는 useState라는 함수를 사용하여 함수 컴포넌트에서도 state를 사용할 수 있게 되었습니다. 사용법은 조금 다르며, 이 과정에서 Hooks라는 것을 사용하게 됩니다. Hooks의 종류는 다양하지만, 지금은 useState에 대해서만 이야기하고 나머지는 8장에서 다루게 됩니다.

 

3.4.2.1 배열 비구조화 할당

 먼저 배열 비구조화 할당에 대해 알아봅시다. 배열 비구조화 할당은 앞서 나온 객체 비구조화 할당과 비슷합니다. 즉, 배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해 주는 문법입니다.

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

위는 array 안에 있는 값을 one과 two에 담아 주는 코드입니다. 이를 배열 비구조화 할당을 사용하면 다음과 같이 표현할 수 있습니다.

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

 

3.4.2.2 useState 사용하기

 다음 코드를 봅시다.

import React, { Component, 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;

useState 함수의 인자에는 상태의 초깃값을 넣어 줍니다. 값의 형태는 숫자, 문자열, 객체, 배열 등 자유입니다. 함수를 호출하면 배열이 반환되며, 배열의 첫 번째 원소는 현재 상태이고 두 번째 원소는 상태를 바꾸어 주는 함수입니다. 이 함수를 세터(setter) 함수라고 부릅니다.

위 Say 컴포넌트를 App에서 렌더링한 결과

 

3.4.2.3 한 컴포넌트에서 useState 여러 번 사용하기

import React, { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage('안녕하세요!');
  const onClickLeave = () => setMessage('안녕히 가세요!');
  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 }
 ];
 len 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 연산자라 불리는 ...을 사용하여 처리하고, 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용합니다.

 

3.6 정리

 props와 state는 둘 다 컴포넌트에서 사용하며 렌더링할 데이터를 담고 있지만 역할은 매우 다릅니다. props는 부모 컴포넌트가 설정하고, state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있습니다.

props를 사용한다고 해서 값이 무조건 고정적이진 않습니다. 부모 컴포넌트의 state를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하면 props도 유동적으로 사용할 수 있습니다.

참고) 앞으로 새로운 컴포넌트를 만들 때는 함수 컴포넌트와 Hooks를 사용하는 것을 권장합니다.

 

 

 

Quiz

1. 컴포넌트를 선언하는 방식으로 OO 컴포넌트와 OOO형 컴포넌트가 있습니다.
2. props는 OO 컴포넌트에서 설정할 수 있습니다.
3. props 값을 따로 지정하지 않게 된다면, ______________를 통해 기본값을 설정할 수 있습니다.
4. _________은 컴포넌트 태그 사이의 내용을 보여주는 props입니다.
5. ES6의 ______________ 문법을 사용하면 props 내부 값을 바로 추출할 수 있게 됩니다.
6. 컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 ____________를 사용합니다
7. _____는 컴포넌트 내부에서 바뀔 수 있는 값을 의미합니다.

8. 비구조화 할당 문법을 사용해 props 값을 바로 추출할 수 있도록 아래 코드를 수정하시오.

const MyComponent = props => {
  return (
    <div>
      children 값은 {props.children}입니다.
    </div>
  );
};

export default MyComponent;

 

9. 다음 코드를 constructor 메서드 선언 없이 state 초기값을 설정하는 코드로 수정하시오.

import React, { Component } from 'react';

class Counter extends Component {
   constructor (props) {
      super(props);
      this.state = {
          number: 0,
          fiexdNumber: 0
        };
      }
        
(...)

 

Answer

1. 함수, 클래스
2. 부모
3. defaultProps
4. children
5. 비구조화 할당
6. propTypes
7. state

8.

const MyComponent = props => {
  const { children } = props;
  return (
    <div>
      children 값은 {children}입니다.
    </div>
  );
};

export default MyComponent;

9.

import React, { Component } from 'react';

class Counter extends Component {
  state = {
      number: 0,
      fiexdNumber: 0
  };
  
(...)

 


Corner React Starter 3

Editor: 머핀

728x90

관련글 더보기