React v16

기존 React 개발 시, state를 사용해야 하는 부분은 클래스형 컴포넌트를 사용하고, 간단하게 state 사용 없이 데이터를 보여주는 부분은 함수형 컴포넌트를 사용했습니다.

이번 Hook 기능이 업데이트되면서 공식 문서에는 클래스 컴포넌트 사용을 지양하고, 함수형 컴포넌트에서 Hook을 사용해 state 제어하는 것을 권장하고 있습니다.

Hook은 리액트 16.8에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능들을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해줍니다.

이번 포스팅에서는 새로운 기능들에 대해 배워보면서 어떤 장점이 있고, 또 진행 중인 프로젝트에 어떻게 사용하면 좋을지 알아보겠습니다.

useState

함수형 컴포넌트에서도 state를 사용할 수 있게 해줍니다.

하나의 useState 함수는 하나의 state 값만 관리할 수 있기 때문에 여러 개의 state 값을 사용해야 한다면 useState를 여러 번 사용하시면 됩니다.

const [value, setValue] = React.useState(0)
const [name, setName] = React.useState('이름')

기존 클래스 컴포넌트의 state 사용법

import * as React from "react";

export class ClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0
    };
  }

  public render() {
    return <React.Fragment>{this.state.value}</React.Fragment>;
  }
}

Hook을 사용한 함수형 컴포넌트 state 사용법

import * as React from "react";

export const FunctionalComponent = () => {
  // useState는 인자로 초기값을 받고, 현재 값(value)와 값을 업데이트할 수 있는 함수(setValue)를 반환해줍니다.
  const [value, setValue] = React.useState(0);

  return (
    // 클래스형 컴포넌트처럼 this를 붙이지 않아도 됩니다.
    <React.Fragment>{value}</React.Fragment>
  );
};

useEffect

클래스형 컴포넌트에서 Life Cycle에 해당하는 로직들을 useEffect를 사용하여 구현할 수 있습니다.

Hook을 사용한 함수형 컴포넌트에서 Life Cycle 사용법

import * as React from "react";

export const FunctionalComponent = () => {
  const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);

  React.useEffect(() => {
    // componentDidmount
    window.addEventListener("resize", resizeHandle);
    return () => {
      // componentWillUnMount
      window.removeEventListener("resize", resizeHandle);
    };
  }, []);
  <!-- useEffect 인자의 배열에 아무것도 넣지 않는다면 componentDidMount + componentWillUnmount 처럼 동작합니다. -->

  React.useEffect(() => {

    ~~~~~~~~~~~~~~~~~~~~~~

  })
  <!-- useEffect 인자를 비워놓으면 매번 render 할 때마다 실행됩니다. -->

  React.useEffect(() => {

    ~~~~~~~~~~~~~~~~~~~~~~

  }, [props.value])
  <!-- 특정 프로퍼티에 대해서 componentWillReceiveProps 처럼 동작합니다. -->
};

Context

Context는 컴포넌트 간에 전역적인 데이터를 공유할 수 있도록 고안된 기능입니다.

createContext, useContext 기능을 사용하면 더 쉽게 사용할 수 있습니다.

import React, { createContext, useContext } from "react";

const AppContext = createContext('컨텍스트');

export const App = () => {
  return (
    <React.Fragment>
      <ChildComponent />
    </React.Fragment>
  );
};

const ChildComponent = () => {
  const context = useContext(AppContext);
  return (
    <React.Fragment>
      {context}
      </React.Fragment>
  )
};

결과 화면

useCallback

함수형 컴포넌트 내부에 이벤트 핸들러 함수가 필요할 때 사용합니다.

useCallback을 사용하지 않고 함수를 생성할 수 있지만, 문제가 있습니다.

const Example = () => {
  const [value , setValue] = React.useState(0)

  // 버튼을 클릭 할 경우 value의 값이 1씩 증가
  const handle = () => {
    setValue(value++)
  }

  return (
    <React.Fragment>
      <button onClick={handle}>버튼</button>
      {value}
    </React.Fragment>
  )
}

이렇게 선언을 하게 되면 컴포넌트가 리렌더링 될 때마다 이 함수들이 새로 생성되기 때문에 최적화가 필요합니다.

const Example = () => {
  const [value , setValue] = React.useState(0)

  const handle = React.useCallback(() => {
    setValue(value++)
  }, [value])

  return (
    <React.Fragment>
      <button onClick={handle}>버튼</button>
      {value}
    </React.Fragment>
  )
}

useCallback의 첫번째 파라미터에는 함수를 넣어주고, 두번째 배열 파라미터에는 함수 내용에서 의존하고 있는 state를 넣어주어야 합니다.

useMemo

useMemo를 사용하면 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있습니다. 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고 바뀌지 않았을 경우 연산했던 결과를 다시 사용하는 방식입니다.

import React from "react";

// 배열의 평균을 구해주는 함수
const getAverage = numbers => {
  if (numbers.length === 0) return 0;

  const sum = numbers.reduce((a, b) => Number(a) + Number(b));
  return sum / numbers.length;
};

export const App = () => {
  const [value, setValue] = React.useState(0);
  const [valueList, setValueList] = React.useState([]);

  const onChange = React.useCallback(
    (e: any) => {
      setValue(e.target.value);
    },
    [value]
  );

  const onAdd = React.useCallback(() => {
    if (value === 0) {
      return;
    }
    const nextList = valueList.concat(value);
    setValueList(nextList);
    setValue(0);
  }, [valueList, value]);

  const average = React.useMemo(() => getAverage(valueList), [valueList]);

  return (
    <React.Fragment>
      <input value={value} onChange={onChange} />
      <button onClick={onAdd}>추가</button>
      {valueList.map((value, index) => (
        <div key={index}>{value}</div>
      ))}

      <div>평균값 : {average} </div>
    </React.Fragment>
  );
};

useMemo의 두 번째 배열 파라미터에 넣은 상태(valueList)가 바뀔 때만 getAverage가 호출됩니다.

Reference