TIL 22-05-04

thisisyjin·2022년 5월 4일
0

TIL 📝

목록 보기
39/113

React.js

Today I Learned ... react.js

🙋‍♂️ Reference Book

🙋‍ My Dev Blog


리액트를 다루는 기술 DAY 17

  • React + Redux

React + Redux Project

지난 포스팅에 이어서 진행함.

Redux-actions

액션 생성함수를 더 짧은 코드로 작성하기 위함.

1. redux-actions 설치

yarn add redux-actions

2. counter 모듈에 적용

액션 생성함수

import { createAction } from 'redux-actions';

// 수정 전
export const increase = () => ({ type: INCREASE });

// 수정 후
export const decrease = createAction(INCREASE);

-> 매번 객체를 직접 만들어 줄 필요 없이, type만 적어주면 간단하게 액션 생성 함수를 선언할 수 있다.

reducer 함수

import { createAction, handleActions } from 'redux-actions';

// 수정 전
const counter = (state = initialState, action) =>{
    switch (action.type) {
        case INCREASE:
            return {
                number: state.number + 1
            };
        case DECREASE:
            return {
                number: state.number - 1
            };
        default:
            return state;
    }
}

// 수정 후
const counter = handleActions(
    {
        [INCREASE]: (state, action) => ({ number: state.number + 1 }),
        [DECREASE]: (state, action) => ({ number: state.number + 1 }),
    },
    initialState,
);

⚡️handleActions

첫번째 인자로는 [액션타입]: state 업데이트 함수 형태의 객체를 넣어주고, (액션 개수대로)
두번째 인자로는 초기값인 initialState를 넣어준다.

3. todos 모듈에 적용

  • 액션 생성 함수를 교체할 때, 각 액션 생성함수에서 파라미터를 필요로 한다.
  • createAction으로 액션을 만들면, 필요한 추가 데이터 (type필드 외에)는 payload라는 이름을 사용함.

예>

const MY_ACTION = 'module/MY_ACTION';
const myAction = createAction(MY_ACTION);
const action = myAction('hello world');

// 결과 = { type: MY_ACTION, payload: 'hello world' }

만약 payload 가 아닌 다른 이름으로 짓고 싶다면,
아래와 같이 createAction의 두번째 인자에 함수를 따로 선언해서 넣어줌.

액션 생성함수

// 수정 전
export const changeInput = input => ({
    type: CHANGE_INPUT,
    input
});

// 수정 후
export const changeInput = createAction(CHANGE_INPUT, input => input);
// 🔺 changeInput() 의 인자로 들어간 것이 input이라는 이름으로 들어가게 됨.
// 수정 전
export const insert = text => ({
    type: INSERT,
    todo: {
        id: id++,
        text,
        done: false
    }
});

// 수정 후
export const insert = createAction(INSERT, text => ({
    id: id++,
    text,
    done: false
}));

reducer 함수

// 수정 전

const todos = (state = initialState, action) => {
    switch (action.type) {
        case CHANGE_INPUT:
            return {
                ...state,
                input: action.input
            };
        case INSERT:
            return {
                ...state,
                todos: state.todos.concat(action.todo)
            };
        case TOGGLE:
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.id ? { ...todo, done: !todo.done } : todo
                )
            };
        case REMOVE:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.id)
            };
        default:
            return state;
    }
}

// 수정 후
const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, action) => ({ ...state, input: action.payload }),
        [INSERT]: (state, action) => ({
            ...state,
            todos: state.todos.concat(action.payload),
        }),
        [TOGGLE]: (state, action) => ({
            ...state,
            todos: state.todos.map(todo =>
                todo.id === action.payload ? { ...todo, done: !todo.done } : todo,
            ),
        }),
        [REMOVE]: (state, action) => ({
            ...state,
            todos: state.todos.filter(todo => todo.id !== action.payload),
        }),
    },
    initialState
);
  • 모든 추가 데이터 값을 action.payload로 관리하므로, 헷갈릴 수 있음.
  • action.payload의 이름을 구조분해 할당으로 다시 설정.
import { createAction, handleActions } from "redux-actions";

const CHANGE_INPUT = 'todos/CHANGE_INPUT';
const INSERT = 'todos/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';

export const changeInput = createAction(CHANGE_INPUT, input => input);

let id = 3;

export const insert = createAction(INSERT, text => ({
    id: id++,
    text,
    done: false
}));

export const toggle = createAction(TOGGLE, id => id);
export const remove = createAction(REMOVE, id => id);

const initialState = {
    input: '',
    todos: [
        {
            id: 1,
            text: '리덕스 기초 배우기',
            done: true
        },
        {
            id: 2,
            text: '리액트와 리덕스 사용하기',
            done: false
        },
    ]
};

const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, {payload: input}) => ({ ...state, input }),
        [INSERT]: (state, { payload: todo }) => ({
            ...state,
            todos: state.todos.concat(todo),
        }),
        [TOGGLE]: (state, {payload: id}) => ({
            ...state,
            todos: state.todos.map(todo =>
                todo.id === id ? { ...todo, done: !todo.done } : todo,
            ),
        }),
        [REMOVE]: (state, {payload: id}) => ({
            ...state,
            todos: state.todos.filter(todo => todo.id !== id),
        }),
    },
    initialState
);
    
export default todos;

immer 라이브러리 적용

  • spread(...)로 불변성 유지 필요함.
  • 객체의 깊이가 깊어지면 불편해짐.
yarn add immer

todos.js (수정)

const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, { payload: input }) => 
            produce(state, draft => {
                draft.input = input;
            }),
        [INSERT]: (state, { payload: todo }) => 
            produce(state, draft => {
                draft.todos.push(todo);
            }),
        [TOGGLE]: (state, {payload: id}) => 
            produce(state, draft => {
                const todo = draft.todos.find(todo => todo.id === id);
                todo.done = !todo.done;
           }),
        [REMOVE]: (state, { payload: id }) => 
            produce(state, draft => {
                const index = draft.todos.findIndex(todo => todo.id === id);
                draft.todos.splice(index, 1);
            })
    },
    initialState
);

Hooks로 컨테이너 생성

  • react-redux 라이브러리에 내장된 Hooks.
  • connect보다 Hooks를 사용하는 것을 권장함.

1. useSelector

  • state 조회.
  • mapStateToProps와 형태가 똑같음.

2. useDispatch

  • dispatch() 사용.
  • mapDispatchToProps 처럼 사용함.
  • const dispatch = useDispatch();를 한 후
    dispatch(액션객체) 를 하면 됨.

CounterContainer.js

import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";

const CounterContainer = () => {
    const number = useSelector(state => state.counter.number);
    const dispatch = useDispatch();
    return (
        <Counter number={number}
            onIncrease={() => dispatch(increase())}
            onDecrease={() => dispatch(decrease())}
        />
    );
};

export default CounterContainer;

성능 최적화를 위해 dispatch() 함수에 React.useCallback을 감싸주는 것을 권장.

3. useStore

  • 리덕스 store 사용.
  • const store = useStore();를 한 후
    store.dispatch();이나 store.getState(); 등 직접 사용 가능.

-> 잘 안씀.


커스텀 Hook 생성

useActions.js

lib/useActions.js

import { bindActionCreators } from "redux";
import { useDispatch } from "react-redux";
import { useMemo } from "react";

export default function useActions(actions, deps) {
    const dispatch = useDispatch();
    return useMemo(
        () => {
            if (Array.isArray(actions)) {
                return actions.map(a => bindActionCreators(a, dispatch));
            }
            return bindActionCreators(actions, dispatch);
        },
        // eslint-disable-next-line react-hooks/exhaustive-deps
        deps ? [dispatch, ...deps] : deps
    );
}
  • 액션 생성함수를 액션을 dispatch하는 함수로 바꿔줌.

useActions(actions, deps)

  • 첫번째 인자는 action 생성함수로 이루어진 배열.
  • 두번째 인자는 deps 배열.
  • useMemo로 감싸주어서 성능최적화를 해줌. (리턴 값을 기억. deps가 변할때마다 다시 저장)

connect() vs. useSelector

connectuseSelector
컴포넌트 리렌더링시 props가 안바뀌었다면 알아서 리렌더링 X따로 성능최적화가 필요함. (컨테이너를 React.memo로 감싸줌)
profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글