본문 바로가기

이것저것 스터디📚/React - 공식 문서

React 공식문서 -Scaling Up with Reducer and Context(Reducer와 Context로 확장하기)

Combining a reducer with context(reducer와 context를 결합하기)

예를 들어 reducer로 state를 관리하는 경우에, App.js와 같이 앱 상단에 reducer 함수를 정의했는 경우, 다른 컴포넌트에서 state를 변경하려면 prop을 통해 state와 state를 변경할 수 있는 이벤트 핸들러를 명시적으로 전달해야 한다.

 

이때, state와 dispatch 함수를 props를 통해 전달하는 대신 context에 넣어 사용한다면 "prop drilling" 없이 모든 컴포넌트 트리에서 task를 읽고 dispatch 함수를 실행할 수 있다.


Step 1: Create the context(Context 생성하기)



useReducer는 현재 state와 state를 업데이트할 수 있는 dispatch 함수를 반환한다.

 

만약 트리를 통해 전달하려면, 두 개의 별개의 context를 생성해야 한다.

import { createContext } from 'react';

// 현재 state를 담을 context
export const TasksContext = createContext(null);

// state를 업데이트할 dispatch를 담을 context
export const TasksDispatchContext = createContext(null);

 


Step 2: Put state and dispatch into context(State와 dispatch 함수를 context에 넣기)

 

- useReducer를 통해 반환된 state와 dispatch를 context provider를 통해 아래 트리 전체에 전달.

import { TasksContext, TasksDispatchContext } from './TasksContext.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
  // ...
  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        ...
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

Step 3: Use context anywhere in the tree(트리 안에서 context 사용하기)

- context를 통해 state와 dispatch를 전달할 수 있기 때문에, props를 통해서 전달할 필요가 없다.

- state와 dispatch가 필요한 컴포넌트에서 context의 state와 dispatch 함수를 읽고 호출할 수 있다.

export default function TaskList() {
  const tasks = useContext(TasksContext);
  // ...

export default function AddTask() {
  const [text, setText] = useState('');
  const dispatch = useContext(TasksDispatchContext);
  // ...
  return (
    // ...
    <button onClick={() => {
      setText('');
      dispatch({
        type: 'added',
        id: nextId++,
        text: text,
      });
    }}>Add</button>
    // ...

Moving all wiring into a single file(하나의 파일로 합치기)

- 필수는 아니지만, reducer와 context를 모두 하나의 파일에 작성하면 컴포넌트들을 조금 더 정리할 수 있다.

- reducer를 같은 파일로 옮기고 provider 컴포넌트를 새로 선언한다.(이 컴포넌트는 모든 것을 하나로 묶는 역할을 하게 된다)

* 예시 코드

아래 예시 코드에서는 context와 reducer가 모두 하나의 파일에 있고, 컴포넌트는 데이터를 어디서 가져오는지가 아닌 무엇을 보여줄 것인지에 집중할 수 있다.

import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);

const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

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);
    }
  }
}

const initialTasks = [
  { id: 0, text: 'Philosopher’s Path', done: true },
  { id: 1, text: 'Visit the temple', done: false },
  { id: 2, text: 'Drink matcha', done: false }
];

* context에 사용하기 위한 useTasks 또는 useTaskDispatch와 같은 커스텀 훅을 만들 수도 있다. 커스텀 훅 안에서도 useContext 등 다른 Hook을 사용할 수 있다.


* 참고 : React 공식문서(https://react-ko.dev/learn)