RTK과 함께 Context API 잘 사용하기

김민찬·2023년 8월 30일
0

React

목록 보기
12/14
post-thumbnail

RTK

  • 이전 코드에서는 리덕스를 사용할 때 가장 귀찮은 보일러플레이트 코드를 줄이기 위해서 redux-actions를 사용하고, Reducer는 반드시 순수 함수여야 한다는 규칙을 지키기 위해서, 그리고 불변성을 지키기 위해서 immer를 사용했었습니다.

예시 코드

import { createActions, handleActions } from 'redux-actions';
import produce from 'immer';

const SET_SOMETHING = 'summary/SET_SOMTHING';
// ...

export const { setSomething } = createActions(
	{
		SET_SOMETHING: somthing => something,
		// ...
	},
	{
		prefix: 'summary',
	}
);

const summaryReducer = handleActions(
	{
		[SET_SOMETHING]: (state, action) => {
			return produce(state, draft => {
				draft.something = action.payload;
			});
		},
		// ...
	},
	initalState
);

export default summaryReducer;

redux-actions

createAction

  • redux-actions에서 제공하는 createAction은 액션 생성자를 자동으로 만들어 주는 함수입니다.
  • 위에 코드처럼 prefix 값을 붙여서 쉽게 action을 생성할 수 있습니다.
  • 커링 함수로 구현되어 있어서 액션이 갖고 있을 수 있는 변수를 payload 로 통일하면서, 액션을 생성하는 것을 자동화할 수 있습니다.

handleActions

  • actiontype에 따라 다른 작업을 하기 위해서 보통 switch 문을 사용합니다.
  • case 마다 함수를 정의해야 해서 코드 중첩이 생겨서 가독성이 떨어집니다.
  • 이러한 문제를 해결해 주는 것이 handleActions 입니다.
  • 첫 번째 파라미터로 액션에 따라 실행 할 함수들을 가진 객체, 두 번째 파라미터로 initalState 를 넣어주면 됩니다.

Immer

  • immer 는 불변성 유지를 쉽게 도와주는 라이브러리 입니다.
  • 새로운 2중, 3중으로 되어있는 객체도 새로운 참조값으로 깊은 복사를 해줍니다.
  • immer는 첫 번째 파라미터로 수정하고 싶은 객체/배열, 두 번째 파라미터로 첫 번째 파라미터에 할당된 객체/배열을 바꾸는 함수를 받습니다.
  • immer는 핵심 원리는 copy-on-writeProxy에 있는데, copy-on-wirte은 자원을 공유하다 수정해야 할 경우가 발생하면 자원의 복사본을 쓰게하는 개념이고, Proxy객체를 이용해서 원본 객체인 상태 객체 대신 Proxy 객체를 대신 변경합니다.
import produce from 'immer';

const nextState = produce(state, draft => {
  draft[1].floor = 'Ground Floor';
});

RTK createSlice

  • createSlice는 inital state, reducer 함수들의 객체, 이름을 받고 자동으로 액션 생성자 및 액션 타입 타입으로 리듀서를 자동으로 생성합니다.
    • 생성된 액션 타입은 이름을 이용해서 prefix를 붙여줍니다.
  • RTK의 createSlice는 앞서 언급한 createActioncreateReducer, immer가 내부적으로 내장되어 있습니다.
  • 추가적으로 Ducks 패턴을 강제적으로 유지 시켜 줍니다.
    • Ducks 패턴은 구조 중심이 아니라 기능 중심으로 파일을 나눠서, 단일 기능을 작성할 때나 기능을 수정할 때 하나의 파일만 다루게 하면 되게 직관적인 코드 작성을 도와주는 패턴입니다.
    • Ducks 패턴의 장점은 한 파일에 모든 기능을 작성하므로 코드가 길어지는데 createSlice는 코드 길이를 줄여줘서 가독성에도 소소한 향상을 줍니다.

예시 코드

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value: 0 
};

const {actions, reducer} = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value++
    },
    decrement: (state) => {
      state.value--
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = actions;
export default reducer;

Context API와 함께 사용

  • RTKContext API 사용 코드와 Redux 코드 모두에서 사용하자고 제안한 이유는 기존 코드의 가독성과 때문입니다.
    • 기존 코드를 알기 위해서는 immerredux-actions 라이브러리에 대한 선행 이해가 필요합니다.
    • Redux 쪽 코드만 바꾸기에는 RTKcreateSlice가 리턴 하는 actionreducerContext API 에 적용하면 Redux 코드와 같아져서 자연스럽게 적응해서 개발 생산성을 늘릴 수 있겠다는 생각을 했습니다.
  • Context 사용 부분을 독립적으로 한 파일로 분리 시킴으로써 데이터를 어디서 가져오는지가 아닌 무엇을 보여줄 것인지에 집중할 수 있도록 정리할 수 있습니다.
  • 추가적으로 context를 사용하기 위한 커스텀 훅 들을 내보내고, 필요할 때 하위 컴포넌트에서 쉽게 사용해서 읽을 수 있습니다.
import React, { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);

const TasksDispatchContext = createContext(null);

export const useTasks = () => useContext(TasksContext);

export const useTasksDispatch = () => useContext(TasksDispatchContext);

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

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

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

const { actions, reducer } = createSlice({
    name: 'tasks',
    initialState,
    reducers: {
        added: (state, action) => {
            state.push(action.payload);
        },
        changed: (state, action) => {
            state = state.map(task => {
                if (task.id === action.payload.id) {
                    return action.payload;
                }
                return task;
            })
        },
        deleted: (state, action) => {
            state = state.filter(task => task.id !== action.payload.id);
        },
    }
});

export const {
	added,
	changed,
	deleted,
} = actions;
export default reducer;

참고자료

Reducer와 Context로 앱 확장하기 – React

4-2. redux-actions 를 통한 더 쉬운 액션관리 · GitBook

[React] Redux - Ducks 패턴

불변 객체와 immer

profile
두려움 없이

0개의 댓글