Today I Learned ... react.js
🙋♂️ Reference Book
🙋 My Dev Blog
리액트를 다루는 기술 DAY 17
- React + Redux
지난 포스팅에 이어서 진행함.
액션 생성함수를 더 짧은 코드로 작성하기 위함.
yarn add redux-actions
import { createAction } from 'redux-actions';
// 수정 전
export const increase = () => ({ type: INCREASE });
// 수정 후
export const decrease = createAction(INCREASE);
-> 매번 객체를 직접 만들어 줄 필요 없이, type만 적어주면 간단하게 액션 생성 함수를 선언할 수 있다.
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를 넣어준다.
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
}));
// 수정 전
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
로 관리하므로, 헷갈릴 수 있음.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;
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
);
- react-redux 라이브러리에 내장된 Hooks.
- connect보다 Hooks를 사용하는 것을 권장함.
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
을 감싸주는 것을 권장.
-> 잘 안씀.
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
);
}
useActions(actions, deps)
- 첫번째 인자는 action 생성함수로 이루어진 배열.
- 두번째 인자는 deps 배열.
useMemo
로 감싸주어서 성능최적화를 해줌. (리턴 값을 기억. deps가 변할때마다 다시 저장)connect | useSelector |
---|---|
컴포넌트 리렌더링시 props가 안바뀌었다면 알아서 리렌더링 X | 따로 성능최적화가 필요함. (컨테이너를 React.memo 로 감싸줌) |