리액트를 다루는 기술 3장

riverkim·2022년 5월 30일
0
post-thumbnail

이 글은 책 리액트를 다루는 기술을 개인적으로 정리한 글 입니다.

컴포넌트

리액트는 여러가지 컴포넌트로 구성되어 있음

컴포넌트의 기능

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

컴포넌트를 선언하는 방식은 두 가지

  • 하나는 함수형 컴포넌트
  • 또 다른 하나는 클래스형 컴포넌트입니다.

클래스형 컴포넌트

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를 반환해야 함

함수형 컴포넌트의 장점

  • 클래스형 컴포넌트보다 선언하기가 쉬움
  • 메모리 자원도 클래스형 컴포넌트보다 덜 사용
  • 프로젝트를 배포할 때도 함수형 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작음(중요X)

함수형 컴포넌트의 주요 단점

  • state와 라이프사이클 API의 사용이 불가능(Hooks 도입 후 해결)

리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수형 컴포넌트와 Hooks를 사용하도록 권장
하지만 그렇다고 해서 클래스형 컴포넌트가 사라지는 것은 아니므로 클래스형 컴포넌트의 기능은 알아야 함

props

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

JSX 내부에서 props 렌더링

props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용
props를 렌더링할 때 2장에서 배웠던 것처럼 JSX 내부에서 { } 감싸서 사용

import React from ‘react‘;

// MyComponent 컴포넌트에서 name이라는 props를 렌더링하도록 설정
const MyComponent = props => {
return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};



export default MyComponent;

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

App 컴포넌트에서 props 값을 지정

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

props 기본값 설정: defaultProps

props 값을 따로 지정하지 않았을 때 보여 줄 기본값을 설정하는 defaultProps


import React from 'react';
 
const MyComponent = props => {
  return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};
 
MyComponent.defaultProps = {
  name: '기본 이름'
};
 
export default MyComponent;

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

컴포넌트 태그 사이의 내용을 보여 주는 props -> children

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

MyComponent 내부에서 보여 주려면 props.children 값 사용

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

MyComponent에서 props 값을 조회할 때마다 props.name, props.children과 같이 props.이라는 키워드를 앞에 붙여 사용하지 않고,
ES6의 비구조화 할당 문법을 사용하여 내부 값을 바로 추출하는 방법
객체에서 값을 추출하는 문법을 비구조화 할당(destructuring assignment) 또는 구조 분해 문법이라 함

import React from 'react';
 
const MyComponent = props => {
  const { name, children } = props; 
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
    </div>
  );
};
 
MyComponent.defaultProps = {
  name: '기본 이름'
};
 
export default MyComponent;

함수의 파라미터 부분에서도 사용
만약 함수의 파라미터가 객체라면 그 값을 바로 비구조화해서 사용

import React from 'react';
 
const MyComponent = ({ name, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
    </div>
  );
};
 
MyComponent.defaultProps = {
  name: '기본 이름'
};
 
export default MyComponent;

propTypes를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입(type)을 지정할 때는 propTypes를 사용
컴포넌트의 propTypes를 지정하는 방법은 defaultProp을 설정하는 것과 비슷

우선 propTypes를 사용하려면 코드 상단에 import 구문을 사용 불러옴

import React from ‘react‘;
import PropTypes from ‘prop-types‘;


const MyComponent = ({ name, children }) => {
  return ();
};



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



MyComponent.propTypes = {
  name: PropTypes.string // name 값은 무조건 문자열(string) 형태로 전달해야 된다는 것을 의미
};



export default MyComponent;

isRequired를 사용하여 필수 propTypes 설정

propTypes를 지정하지 않았을 때 경고 메시지를 띄워 주는 작업
propTypes를 지정할 때 뒤에 isRequired를 사용

import React from ‘react‘;
import PropTypes from ‘prop-types‘;


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



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



MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired // favoriteNumber 라는 숫자를 필수 props로 지정
};



export default MyComponent;

더 많은 PropTypes 종류

PropTypes에서는 여러 가지 종류를 설정 가능

• array: 배열
• arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열을 의미합니다. 예를 들어 arrayOf(PropTypes.number)는 숫자로 이루어진 배열입니다.
• bool: true 혹은 false 값
• func: 함수
• number: 숫자
• object: 객체
• string: 문자열
• symbol: ES6의 Symbol
• node: 렌더링할 수 있는 모든 것(숫자, 문자열, 혹은 JSX 코드. children도 node PropType입니다.)
• instanceOf(클래스): 특정 클래스의 인스턴스(예: instanceOf(MyClass))
• 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에서 확인

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

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

MyComponent를 클래스형 컴포넌트로 변환

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;

state

리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값

컴포넌트 자신은 해당 props를 읽기 전용으로만 사용
props를 바꾸려면 부모 컴포넌트에서 바꿔야 함

예를 들어 현재 상황에서는 App 컴포넌트에서 MyComponent를 사용할 때 props를 바꾸어 주어야 값이 변경
반면 MyComponent에서는 전달받은 name 값을 직접 바꿀 수 없음

리액트에는 두 가지 종류의 state가 존재
하나는 클래스형 컴포넌트가 지니고 있는 state이고, 다른 하나는 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state

클래스형 컴포넌트의 state

import React, { Component } from ‘react‘;
 
class Counter extends Component {
  constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number: 0
    };
  }
  render() {
    const { number } = this.state; // state를 조회할 때는 this.state로 조회합니다.
    return (
      <div>
        <h1>{number}</h1>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
          onClick={() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}
 
export default Counter;

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

// 컴포넌트의 생성자 메서드
constructor(props) {
  super(props);
  // state의 초깃값 설정하기
  this.state = {
      number: 0
    };
  }

클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출
이 함수가 호출되면 현재 클래스형 컴포넌트가 상속하고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출

그다음에는 this.state 값에 초깃값을 설정해 주었습니다.
컴포넌트의 state는 객체 형식이어야 합니다.

이제 render 함수를 확인해 봅시다.

render() {
  const { number } = this.state; // state를 조회할 때는 this.state로 조회합니다.
  return (
      <div>
        <h1>{number}</h1>
        <button
        // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
        onClick={() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
          this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }

render 함수에서 현재 state를 조회할 때는 this.state를 조회

그리고 button 안에 onClick이라는 값을 props로 넣어 주었는데, 이는 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 해 줍니다.

이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용하여 넣어 주어야 합니다. 함수 내부에서는 this.setState라는 함수를 사용했는데요. 이 함수가 state 값을 바꿀 수 있게 해 줍니다.

코드를 다 작성했으면 Counter 컴포넌트를 App에서 불러와 렌더링하세요. 기존 MyComponent 는 이제 필요 없으니 없애 주겠습니다.

import React from ‘react‘;
import Counter from./Counter‘;


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



export default App;

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

state 객체 안에는 여러 값이 있을 수 있습니다. Counter 컴포넌트를 다음과 같이 한번 수정해 보세요.

import React, { Component } from ‘react‘;


class Counter extends Component {
  constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number: 0,
      fixedNumber: 0
    };
  }
  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회합니다.
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지 않는 값: {fixedNumber}</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
          onClick={() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}



export default Counter;

현재 state 안에 fixedNumber라는 또 다른 값을 추가해 주었습니다. 버튼이 클릭될 때 fixedNumber 값은 그대로 두고 number 값만 바꿀 것인데요. 그렇다고 해서 this.setState 함수를 사용할 때 인자로 전달되는 개체 내부에 fixedNumber를 넣어 주지는 않았습니다. this.setState 함수는 인자로 전달된 객체 안에 들어 있는 값만 바꾸어 줍니다.

코드를 저장하고 브라우저를 열어서 버튼을 눌러 보세요. 맨 위에 있는 숫자만 업데이트되고 하단의 숫자는 잘 고정되어 있나요?

state를 constructor에서 꺼내기

앞에서 state의 초깃값을 지정하기 위해 constructor 메서드를 선언해 주었는데요. 또 다른 방식으로도 state의 초깃값을 지정해 줄 수 있습니다.

코드를 다음과 같이 수정해 보세요.

import React, { Component } from 'react';
 
class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0
  };
  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회합니다.
    return (...);
  }
}
 
export default Counter;

이렇게 하면 constructor 메서드를 선언하지 않고도 state 초깃값을 설정할 수 있습니다. 이 책에서는 앞으로 state를 사용할 때 이 방식을 사용하여 state의 초깃값을 설정하겠습니다.

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

this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트됩니다. 만약 다음과 같이 onClick에 설정한 함수 내부에서 this.setState를 두 번 호출하면 어떻게 될까요?

onClick={() => {
  // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
  this.setState({ number: number + 1 });
  this.setState({ number: this.state.number + 1 });
}}

코드를 위와 같이 작성하면 this.setState를 두 번 사용하는 것임에도 불구하고 버튼을 클릭할 때 숫자가 1씩 더해집니다. this.setState를 사용한다고 해서 state 값이 바로 바뀌지는 않기 때문입니다.

이에 대한 해결책은 this.setState를 사용할 때 객체 대신에 함수를 인자로 넣어 주는 것입니다. this.setState의 인자로 함수를 넣어 줄 때는 코드를 다음과 같은 형식으로 작성합니다.

this.setState((prevState, props) => {
return {
// 업데이트하고 싶은 내용
}
})
여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리킵니다. 만약 업데이트하는 과정에서 props가 필요하지 않다면 생략해도 됩니다.

<button
  // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
  onClick={() => {
    this.setState(prevState => {
      return {
        number: prevState.number + 1
      };
    });
    // 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
    // 아래 코드는 함수에서 바로 객체를 반환한다는 의미입니다.
    this.setState(prevState => ({
      number: prevState.number + 1
    }));
  }}
>
  +1
</button>

살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 { }를 생략하면 됩니다. 예를 들어, 파라미터 a와 b를 받아 와서 합을 구해 주는 함수를 작성하고 싶다면 다음과 같이 작성할 수 있습니다.

const sum = (a, b) => a + b;
onClick에서 두 번째로 this.setState 함수를 사용할 때는 화살표 함수에서 바로 객체를 반환하도록 했기 때문에 prevState => ({ })와 같은 형태로 코드가 이루어집니다.

브라우저에서 버튼을 눌러 보세요. 숫자가 2씩 올라가나요?

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

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

onClick 함수를 다음과 같이 수정해 보세요.

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

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

리액트 16.8 이전 버전에서는 함수형 컴포넌트에서 state를 사용할 수 없었지만 16.8 이후부터는 useState라는 함수를 사용하여 함수형 컴포넌트에서도 state를 사용할 수 있게 됨 -> Hooks

배열 비구조화 할당

배열 비구조화 할당은 이전에 배운 객체 비구조화 할당과 비슷
즉, 배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해 주는 문법


// array 안에 있는 값을 one과 two에 담아 주는 코드
// 기본
const array = [1, 2];
const one = array[0];
const two = array[1];

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

useState 사용하기

배열 비구조화 할당 문법을 알고 나면 useState 사용 방법을 쉽게 이해할 수 있음

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;

useState 함수의 인자에는 상태의 초깃값 넣어줌
함수형 컴포넌트에서는 useState값의 형태가 자유(숫자일 수도, 문자열일 수도, 객체일 수도, 배열일 수도 있음)

useState 함수를 호출하면 배열이 반환

배열의 첫 번째 원소는 현재 상태이고,
두 번째 원소는 상태를 바꾸어 주는 함수(세터(Setter) 함수)

그배열 비구조화 할당을 통해 이름을 자유롭게 설정 가능

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

useState는 한 컴포넌트에서 여러 번 사용해도 상관 X

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;

state를 사용할 때 주의 사항

state 값을 바꾸어야 할 때는 setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 함

// 잘못된 코드입니다.
// 클래스형 컴포넌트에서…
this.state.number = this.state.number + 1;
this.state.array = this.array.push(2);
this.state.object.value = 5;
// 잘못된 코드입니다.
// 함수형 컴포넌트에서…
const [object, setObject] = useState({ a: 1, b: 1 });
object.b = 2;

배열이나 객체 사본을 만들고 그 사본에 값을 업데이트한 후, 그 사본의 상태를 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 연산자라 불리는 ...을 사용하여 처리
배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용

정리

props와 state는 둘 다 컴포넌트에서 사용하거나 렌더링할 데이터를 담고 있으므로 비슷해 보일 수 있지만, 그 역할은 매우 다름

  • props는 부모 컴포넌트가 설정
  • state는 컴포넌트 자체적으로 지닌 값(컴포넌트 내부에서 값을 업데이트 가능)

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

profile
Hello!

0개의 댓글