이전에 리액트 리덕스는 어떻게 활용할 수 있는지 알아보았습니다. 그런데 매번 액션 생성 함수를 만들어주어야할까요?
넘나 귀찮지 않습니까?.. 그리고 리듀서 함수에서 switch문을 통해 여러번 적는 행위도 너무 귀찮아 보입니다.
매번 return 마다 구조분해 문법을 사용하여 state를 가져왔지만, 만약에 state구조가 복잡하다면 이 또한 관리하기 힘들어지게 됩니다.
이것을 해결할 방법! 에 대해서 이번에는 공부해보겠습니다.
● 리덕스 더 편하게 사용하기
npm i redux-actions
먼저 위의 라이브러리를 설치해줍니다.
다음 우리가 만들어주었던 counter.js 모듈로 돌아와 createAction 을 redux-action으로부터 불러와줍니다.
counter.js
import { createAction } from 'redux-actions';
const INCREASE = 'counter/increase';
const DECREASE = 'counter/decrease';
export const increase = createAction(INCREASE)
export const decrease = createAction(DECREASE)
위와 같이 수정 될 수 있습니다. (근데 솔직히 머가 더 편해진건진 잘모르겟네요 ㅎ)
다음 handleActions를 불러와줍니다.
import { createAction, handleAction } from 'redux-actions';
const INCREASE = 'counter/increase';
const DECREASE = 'counter/decrease';
export const increase = createAction(INCREASE)
export const decrease = createAction(DECREASE)
const initialState = {
number : 0
};
const counter = handleAction(
{
[INCREASE] : (state, action) => ({ number : state.number + 1}),
[DECREASE] : (state, action) => ({ number : state.number - 1}),
},
initialState
)
export default counter;
위와 같은 형식으로 줄여질 수 있습니다. 가독성이 조으네요.
이제 todos.js 모듈도 수정해주겠습니다.
todos.js
import { createAction } from 'redux-actions';
const CHANGE_INPUT = 'todos/CHANGE_INPUT';
const INSERT = 'todos/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';
let id = 3;
export const changeInput = createAction(CHANGE_INPUT, input => input);
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);
흠.. payload가 필요할 때는 그다지.. 유용해보이진 않네요^^;;
todos.js
const todos = handleAction(
{
[CHANGE_INPUT] : (state, action) => ({ ...state, input : action.payload}),
[INSERT] : (state, action) => ({ ...state, todos : state.todos.concat(action.payload)}),
[TOGGLE] : (state, action) =>
({ ...state,
todos : 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)
export default todos;
와.. 리듀서함수 부분은 리얼 개똑같네요.. 달라진게 뭐임?;; ㅎㅎ 쓰래기 라이브러리네요.
immer부분은.. 쓰다가 빡쳐서 접엇습니다. 이 라이브러리는 쓰지 않갰읍니다.
● Hooks를 사용하여 컨테이너 컴포넌트 만들기
먼저 useSelector 훅을 react-redux에서 불러와줍니다.
다음 아래와 같이 설정해주면 됩니다!
CounterContainer.js
import Counter from '../components/Counter';
import { useSelector } from 'react-redux';
import { decrease, increase } from '../modules/counter';
const CounterContainer = ({number, increase, decrease})=>{
const number = useSelector(state => state.counter.number);
return (
<Counter number={number} />
)
}
export default Counter
그럼 dispatch 함수는 어떻게 불러와줄까요?
import Counter from '../components/Counter';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } 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 Counter
아래와 같이 불러와주면 됩니댜.. 넘나 편하죠?
근데 connect함수로 불러오는것도 나쁘지는 않은 선택입니다!
근데 useDispatch로 함수를 불러오게 된다면 useCallback을 써주는 것이 좋습니다.
import Counter from '../components/Counter';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from '../modules/counter';
import { useCallback } from 'react';
const CounterContainer = ()=>{
const number = useSelector(state => state.counter.number);
const dispatch = useDispatch();
const onIncrease = useCallback(()=>{ dispatch(increase())}, [dispatch]);
const onDecrease = useCallback(()=>{ dispatch(decrease())}, [dispatch]);
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease}/>
)
}
export default Counter
useDispatch를 쓸 때 꼭 습관을 들이자!!!!!!!!!!
다음은 TodosContainer.js를 수정해주겠습니다.
TodosContainer.js
import { useSelector, useDispatch } from 'react-redux';
import { changeInput, insert, remove, toggle } from '../modules/todos';
import { useCallback } from 'react';
import Todos from '../components/Todos';
const TodosContainer = ()=>{
const {input, todos} = useSelector(({todos})=> ({
input : todos.input,
todos : todos.todos
}))
const dispatch = useDispatch();
const onChangeInput = useCallback((input)=>{ dispatch(changeInput(input))}, [dispatch]);
const onInsert = useCallback((text)=>{ dispatch(insert(text))}, [dispatch]);
const onToggle = useCallback((id)=>{ dispatch(toggle(id))}, [dispatch]);
const onRemove = useCallback((id)=>{dispatch(remove(id))}, [dispatch])
return <Todos
input={input}
todos={todos}
onChangeInput={onChangeInput}
onInsert={onInsert}
onToggle={onToggle}
onRemove={onRemove}/>
}
export default TodosContainer;
근데 쩜.. 번거롭네용 구춍?..ㅎㅎ 나같으면.. connect 쓰겟다 십기도 합니다..
● connect함수와 useSelector, useDispatch의 차이점
connect 함수를 사용하여 컨테이너 컴포넌트를 만들었을 경우, 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링 될 때마다 해당 컨테이너의 props가 변하지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화 됩니다.
하지만 useSelector, useDispatch는 최적화기능이 없어서 React.memo나 useCallback기능을 활용하여 함수를 메모제이션 해주어야 하는 것 입니다..!
나 갓으면.. connect 쓰갯다..