TIL 22-05-04

thisisyjin·2022년 5월 4일
0

TIL 📝

목록 보기
39/69

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
🌊 Web Front-end Study Log

0개의 댓글