몽환화

[React] useReducer 물고뜯기 본문

Front/React

[React] useReducer 물고뜯기

hyeii 2024. 5. 3. 17:38

물고뜯기 세번째.

 

useReducer

컴포넌트의 최상위에 호출하고 reducer를 이용해 state를 관리한다

 

그럼 reducer가 뭔데?

한 컴포넌트에서 state 업데이트가 여러 이벤트 핸들러로 분산되는 경우가 있다. 이 경우 컴포넌트를 관리하기 어려워지므로 state를 업데이트하는 모든 로직을 reducer를 사용해 컴포넌트 외부로 단일 함수를 통합해 관리할 수 있다

 

컴포넌트 내부의 state 로직을 외부로 빼서 reducer로 옮길 수 있다

 

 

import { useReducer } from 'react';

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

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialArg, init?);
  // ...

 

매개변수

  • reducer: state가 어떻게 업데이트 되는지 지정하는 함수. 리듀서 함수는 반드시 순수 함수여야 하며, state와 action을 인수로 받아야 하고, 다음 state를 반환해야 한다. state와 action에는 모든 데이터 타입이 할당될 수 있다.
  • initialArg: 초기 state가 계산되는 값. 모든 데이터 타입이 할당될 수 있다. 초기 state가 어떻게 계산되는지는 다음 init 인수에 따라 달라진다.
  • 선택사항 init: 초기 state를 반환하는 초기화 함수. 이 함수가 인수에 할당되지 않으면 초기 state는 initialArg로 설정된다. 할당되었다면 초기 state는 init(initialArg)를 호출한 결과가 할당된다. useState에서의 게으른 초기화 같은 것 

반환값 : useReducer는 2개의 엘리먼트로 구성된 배열을 반환한다

  • state : 첫 번째 렌더링에서의 state는 init 또는 initialArg로 설정된다(init이 없을 경우 initialArg)
  • dispatch 함수 : state를 새로운 값으로 업데이트하고 리렌더링을 일으킨다

 

dispatch 

이 함수는 useReducer에 의해 반환되며, state를 새로운 값으로 업데이트하고 리렌더링을 일으킨다!

dispatch는 유일한 인수 action을 갖는다

  • action: 사용자에 의해 수행된 활동입니다. 모든 데이터 타입이 할당될 수 있습니다. 컨벤션에 의해 action은 일반적으로 action을 정의하는 type 프로퍼티와 추가적인 정보를 표현하는 기타 프로퍼티를 포함한 객체로 구성됩니다.
  • dispatch 함수는 오직 다음 렌더링에 사용할 state 변수만 업데이트한다. 만약 dispatch 함수를 호출한 직후에 state 변수를 읽는다면 호출 이전의 최신화되지 않은 값을 참조한다. 현재 동작하고 있는 코드의 state를 변경하지는 않는다. dispatch로 action을 호출해도 오래된 state값이 출력된다

 

 

 

자 그래서 어떻게 사용하는건지 찬찬히 뜯어보쟈 

 

  1. state를 설정하는 것에서 action을 dispatch 함수로 전달하는 것으로 바꾸기.
  2. reducer 함수 작성하기.
  3. 컴포넌트에서 reducer 사용하기.

1.

function handleAddTask(text) {
  setTasks([...tasks, {
    id: nextId++,
    text: text,
    done: false
  }]);
}

 

위는 기존에 useState를 사용했을 때 이벤트핸들러다

위에서 대충 const [ tasks, setTasks ] = useState()를 사용했겠찌

우리는 이제 state를 버릴거다!

 

state를 설정해 무엇을 할 지 지시하는 대신 이벤트 핸들러에서 action을 전달해 사용자가 방금 한 일을 저장한다

function handleAddTask(text) {
  dispatch({
    type: 'ADDED',
    id: nextId++,
    text: text,
  });
}

 

여기서 dispatch에 넣어준 객체를 action이라고 함. action 객체는 어떤 형태든 될 수 있어

근데 type을 명시한 객체로 만들어서 어떤 행동을 수행할건지 명확히 표시하는게 좋당 그리고 대문자로 쓰더라 

 

2.

reducer함수에 현재의 state값, action 객체를 받고 state를 반환해

 

reducer함수는 이렇게 생겼엉

function yourReducer(state, action) {
  // React가 설정하게될 다음 state 값을 반환합니다.
}

 

뭐암튼 적용해보면

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'ADDED': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

 

실제 경우는 switch문에 case가 여러개 있겠징 

 

지금 tasks랑 action을 인자로 받으니까 컴포넌트 외부에서 선언이 가능해

에러도 던져주게 하자

보통 switch문을 많이 쓴대 if문 잘 안쓴대 

 

 

3.

이제 컴포넌트에 tasksReducer를 연결해

import { useReducer } from 'react';

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

여기서 인자 2개는

  • reducer 함수
  • 초기 state 값

그리고 반환값은

  • state를 담을 수 있는 값
  • dispatch 함수 (사용자의 action을 reducer 함수에게 전달하게 될, state를 변경)

 

 

그래서 리액트 공식 문서에서 예시 코드 전문을 가져와보면

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'ADDED',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'CHANGED',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'DELETED',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}
function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'ADDED': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'CHANGED': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'DELETED': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}
let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false }
];

 

여기서 taskReducer를 다른 파일로 분리하는 것도 가눙하다!

 

 

useReducer의 목적

복잡한 형태의 state를 사전에 정의된 dispatcher로만 수정할 수 있게 만들어 state에 대한 접근은 컴포넌트 내에서만 가능하게 하고 이걸 업데이트하는 것은 외부에 둘 수 있음

단순한 값들을 관리하는건 useState로 충분하지만 이 state가 복잡해진다면 useReducer 짱

 

세번째 인수 게으른초기화는 굳이 안써도 된다.

 

 

그렇다면 Redux와의 비교,,,

추가예정,,,,,

 

 

 

 

 

 

 

참고자료라고 하고 사실상 복붙이라고 읽기 

https://ko.react.dev/reference/react/useReducer

https://ko.react.dev/learn/extracting-state-logic-into-a-reducer

https://www.frontendmag.com/tutorials/usereducer-vs-redux/#google_vignette

 

'Front > React' 카테고리의 다른 글

[React] Recoil 뜯어보기  (0) 2024.05.22
[React] useLayoutEffect, useDebugValue  (0) 2024.05.06
[React] useImperativeHandle 물고뜯기  (0) 2024.05.06
[React] useContext 물고뜯기  (0) 2024.05.02
[React] useRef 물고뜯기  (1) 2024.05.01