[2024.06.13] TIL 38일차

김선민·2024년 6월 13일

useReducer란?

  • useReducer는 useState와 같은 상태 관리, 상태 업데이트 훅이다.
  • 변경할 값이 많을 때, 즉 상태 관리 할 데이터가 많아질 때 사용을 고민해야 한다.

useReducer 기본 형태

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

  • state : 상태 이름 (컴포넌트에서 사용할 상태)
  • dispatch : 상태를 변경 시 필요한 정보를 전달하는 함수
  • reducer : dispatch를 확인해서 state를 변경해주는 함수
  • initialState : state에 전달할 초기 값

<예시 코드>

  import { useReducer } from 'react';

  function reducer(state, action) {
    // ...
  }

  function MyComponent() {
    const [state, dispatch] = useReducer(reducer, { age: 42 });
    // ...

매개변수

  1. reducer : reducer 함수는 상태가 업데이트되는 방식을 지정

    • 순수 함수여야 한다
      • 동일 인자에 대해 항상 동일한 결과를 반환해야 함
      • 외부 상태를 변경하거나 부작용을 일으키면 안됨
    • state와 action을 인자로 받아야 한다.
    • 다음 상태를 반환해야 한다.
    • state와 action은 어떤 유형이든 가능하다.
  2. initialArg : 초기 상태가 계산되는 값

    • 모든 유형의 값이 될 수 있다.
    • 초기 상태를 계산하는 데 사용된다.
      • 구체적인 계산 방법은 init 인자에 의해 결정됨
  1. 선택적 init : 초기 상태를 계산하는 초기화 함수

    • 초기 상태를 반환해야 한다.
    • 사용 방식
      • init 함수가 지정되지 않는 경우, 초기 상태는 initialArg 값으로 설정
      • init 함수가 지정된 경우, 초기 상태는 init(initialArg) 호출 결과로 설정

반환값

  • useReducer는 정확히 두 개의 값을 가진 배열을 반환한다.
    • 현재 state, 첫번째 렌더링 중에는 init(initialArg) 또는 initialArg로 설정
    • state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 dispatch function

주의사항

  1. useReducer 호출 위치
    • useReducer는 훅이므로 다음 위치에서만 호출할 수 있다.
      • 컴포넌트의 최상위 레벨
      • 사용자 정의 훅
    • 반복문이나 조건문 내부에서는 useReducer를 호출할 수 없다.
      • 만약 반복문이나 조건문 내부에서 상태를 관리해야 한다면,
        새 컴포넌트를 만들어서 그 안으로 옮겨야 함
  1. Strict Mode에서의 동작
    • Strict Mode에서는 React가 의도치 않은 불순물(부작용)을 찾기 위해 reducer와
      초기화 함수를 두 번 호출한다.
      • 이 동작은 개발 모드에서만 발생하며, 상용 환경에서는 발생하지 않음
    • reducer와 초기화 함수가 순수 함수라면, 이 동작이 로직에 영향을 미치지 않는다.
      • 두 번 호출된 결과 중 하나는 무시됨

사용법

컴포넌트에 추가하기

  • 컴포넌트 최상위 레벨에서 useReducer를 호출하여 reducer를 state 관리한다.
    import { useReducer } from 'react';

    function reducer(state, action) {
      // ...
    }

    function MyComponent() {
      const [state, dispatch] = useReducer(reducer, { age: 42 });
      // ...
  • useReducer는 정확히 두 개의 항목이 있는 배열을 반환한다.
  1. 이 state 변수는 현재 state -> 처음에 제공한 초기 state로 설정됨
  2. 상호작용에 반응하여 이를 변경할 수 있는 dispatch 함수
  3. 화면에 표시되는 내용을 업데이트 하려면 사용자가 수행한 작업을 나타내는 객체,
    즉, 액션을 사용하여 dispatch를 호출
    function handleClick() {
      dispatch({ type: 'incremented_age' });
    }
  1. React는 현재 state와 액션을 reducer 함수에 전달한다.
  2. Reducer는 다음 state를 계산하고 반환한다.
  3. React는 다음 state를 저장하고, 컴포넌트를 렌더링하고, UI를 업데이트 한다.
    import { useReducer } from 'react';

    function reducer(state, action) {
      if (action.type === 'incremented_age') {
        return {
          age: state.age + 1
        };
      }
      throw Error('Unknown action.');
    }

    export default function Counter() {
      const [state, dispatch] = useReducer(reducer, { age: 42 });

      return (
        <>
          <button onClick={() => {
            dispatch({ type: 'incremented_age' })
          }}>
            Increment age
          </button>
          <p>Hello! You are {state.age}.</p>
        </>
      );
    }

reducer 함수 작성하기

  • Reducer 함수는 다음과 같이 선언된다.
    function reducer(state, action) {
      // ...
    }
  • 그런 다음 state를 계산하고 반환할 코드를 입력해야 한다.
    • 관례상 switch 문으로 작성하는 것이 일반적이다.
    • switch의 각 case에 대해 다음 state를 계산하고 반환해야 한다.
    function reducer(state, action) {
      switch (action.type) {
        case 'incremented_age': {
          return {
            name: state.name,
            age: state.age + 1
          };
        }
        case 'changed_name': {
          return {
            name: action.nextName,
            age: state.age
          };
        }
      }
      throw Error('Unknown action: ' + action.type);
    }
  • 액션이 어떤 형태든 가질 수 있다.
    • 관례상 액션을 식별하는 type 프로퍼티가 있는 객체를 전달하는 것이 일반적이다.
    • 여기에는 reducer가 다음 state를 계산하는데 필요한 최소한의 필수 정보가 포함되어야 한다.
    function Form() {
      const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
 
      function handleButtonClick() {
        dispatch({ type: 'incremented_age' });
      }

      function handleInputChange(e) {
        dispatch({
          type: 'changed_name',
          nextName: e.target.value
        });
      }
      // ...
  • 액션 유형 이름은 컴포넌트에 로컬로 지정된다
    • 각 액션은 아무리 많은 데이터를 변경하게 되더라도 오직 하나의 상호작용만을 기술
    • state의 모양은 임의적이지만 일반적으로 객체나 배열이 될 것 같다

초기 state 재생성 방지하기

  • React는 초기 state를 한 번 저장하고 다음 렌더링에서 이를 무시한다.
    function createInitialState(username) {
      // ...
    }

    function TodoList({ username }) {
      const [state, dispatch] = useReducer(reducer, createInitialState(username));
      // ...
  • createInitialState(username)의 결과는 초기 렌더링에서만 사용되지만, 이후의 모든 렌더링에서도 여전히 이 함수를 호출하게 된다
    • 이는 큰 배열을 만들거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있다.
  • 이 문제를 해결하려면 useReducer 세번 째 인수에 초기화 함수를 전달할 수 있다.
    function createInitialState(username) {
      // ...
    }

    function TodoList({ username }) {
      const [state, dispatch] = useReducer(reducer, username, createInitialState);
      // ...
  • 함수를 호출한 결과인 createInitialState()가 아니라 함수 자체 createInitialState를 전달하고 있다는 점에 유의해야 한다.
    • 이렇게 하면 초기화 후에는 초기 state가 다시 생성되지 않는다.
  • 위의 예에서 createInitialState는 username 인수를 받는다.
    • 초기화 함수가 초기 state를 계산하는 데 아무런 정보가 필요 하지 않는 경우, useReducer의 두번째 인수로 null을 전달할 수 있다.



























profile
웹 프론트엔드

1개의 댓글

comment-user-thumbnail
2024년 6월 14일

오잉 ? ^-^ 이라고 할 뻔 했습니다~

답글 달기