React Hook 1

React 16.8 버전부터 Hook이라는 기능이 추가되면서 함수형 컴포넌트에서도 상태 관리 및 렌더링 직후 작업 설정 등 기존 함수형 컴포넌트에서 할 수 없었던 다양한 작업들을 할 수 있게 되었다.

즉, Hook

  • 함수형 컴포넌트에서 React State와 Life Cycle을 사용할 수 있게 해주는 함수이다.
  • class 안에서는 동작하지 않는다. class 없이 React를 사용할 수 있게 해준다.
  • useState 같은 내장 Hook들을 제공하지만 상태 관련 로직을 재사용하기 위해 Hook을 직접 만드는 것도 가능하다.

자주 사용하는 내장 Hook은 아래와 같다.

1. useState

함수형 컴포넌트에서 상태를 관리해야 한다면 useState를 사용하면 된다. useState를 사용하여 count 변수의 상태를 관리해야 한다면 아래와 같이 작성할 수 있다.

import React, { useState } from 'react';

const Counter = () => {
  // count라는 이름으로 state 변수를 선언하고, 0으로 초기화한다.
  // count 변수의 값을 갱신하려면 setCount를 호출한다.
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>현재 count : {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
    </div>
  );
};
  • useState는 state 변수, 해당 변수를 갱신할 수 있는 함수 두 가지를 반환한다.
  • 위에서는 count라는 state 변수를 선언했고, setCount로 count 값을 갱신한다.
  • useState는 인자로 state 초기 값을 하나 받는다. 위 코드에서 count의 초기값은 0이다.
  • 컴포넌트가 렌더링할 때 오직 한 번만 생성된다. state는 컴포넌트가 다시 렌더링되어도 그대로 유지된다.
  • 대괄호를 이용하여 state 변수를 선언한다.(배열 구조 분해 라는 자바스크립트 문법 사용. MDN web docs - 배열 구조 분해 참고)

각각 다른 이름으로 여러 개의 state를 선언할 수도 있다.

function ExampleWithManyStates() {
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

2. useEffect

useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다. 클래스형 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount와 같은 목적이지만 하나의 API로 통합한 것으로 보면 된다.
useEffect는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다. useEffect를 사용하여 문서 타이틀을 업데이트한다면 아래와 같이 작성해볼 수 있다.

- useEffect 기본 사용: 렌더링되고 난 직후마다 실행
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // componentDidMount, componentDidUpdate와 비슷하다.
  useEffect(() => {
    // 브라우저 API를 이용해 문서의 타이틀을 업데이트.
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
- 마운트될 때만 실행하고 싶을 때

useEffect에서 설정한 함수를 컴포넌트가 화면에 맨 처음 렌더링될 때만 실행하고, 업데이트될 때는 실행하지 않으려면 함수의 두 번째 파라미터로 비어 있는 배열을 넣어 주면 된다.

useEffect(() => {
  console.log('마운트될 때만 실행');
});
- 특정 값이 업데이트될 때만 실행하고 싶을 때
useEffect(() => {
  console.log(name);
}, [name]);
- 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶을 때: 해제 함수 반환
useEffect(() => {
  console.log('effect');
  return () => {
    console.log('cleanup');
  };
});
- 언마운트될 때만 해제 함수를 호출하고 싶을 때
useEffect(() => {
  console.log('effect');
  return () => {
    console.log('cleanup');
  };
}, []);

3. useReducer

useState의 대체 함수. useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용한다.

const [state, dispatch] = useReducer(reducer, initialArg, init);

reducer는 현재 상태, 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수이다. reducer 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜주어야 한다.

count 예제를 reducer를 사용해서 다시 작성한 코드는 아래와 같다.

const initialState = { count: 0 };

function reducer(state, action) {
  // action.type에 따라 다른 작업 수행
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      // 아무 것도 해당되지 않을 때 기존 상태를 반환하거나 에러 발생
      // throw new Error();
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}
  • uesReducer의 첫 번째 파라미터에는 reducer 함수를 넣고, 두 번째 파라미터에는 해당 reducer의 기본값을 넣어준다.
  • useReducer는 state 값과 dispatch 함수를 반환한다. state는 현재 상태, dispatch는 액션을 발생시키는 함수이다.
  • 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼수 있다는 장점이 있다.

관리해야 하는 input 개수가 많아서 useState를 여러 번 사용해야 하는 경우 useReducer를 사용하면 코드를 깔끔하게 유지할 수 있다.

const initialState = { name: '', email: '' };

function reducer(state, action) {
  return {
    ...state,
    [action.name]: action.value
  };
}

const Info = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const { name, email } = state;
  const onChange = e => {
    // event 객체가 지니고 있는 target 값 자체를 액션 값으로 사용.
    dispatch(e.target);
  };

  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input name="email" value={email} onChange={onChange} />
      </div>
      <div>
        <p>
          <b>name: </b> {name}
        </p>
        <p>
          <b>email: </b> {email}
        </p>
      </div>
    </div>
  );
};

reference:

리액트 공식 문서
MDN web docs