안녕하세요! delay100입니다. 이번 포스팅에서는 앞에 17장에서 다뤘던 리덕스 애플리케이션을 가져와서 리덕스를 좀 더 편하게 사용하는 방법을 알아보겠습니다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 17.6장 이후의 내용을 다루고 있습니다. 17장의 프로젝트에서 이어서 작업을 합니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch17/react-redux-tutorial2
1. redux-actions
redux-actions를 이용하면 액션 생성 함수를 더 짧은 코드로 작성할 수 있습니다. 또한 리듀서를 작성 시 switch/case 문이 아닌 handleActions라는 함수를 사용하여 각 액션마다 업데이트 함수를 설정하는 형식으로 작성해 줄 수 있습니다.
- redux-actions 라이브러리 설치
yarn add redux-actions
1-1. counter 모듈에 적용
counter 모듈에는 아래의 2가지를 변경해 적용합니다.
1. 액션 생성 함수 -> createAction 함수
2. 리듀서 함수 -> handleActions 함수
- counter 모듈에 redux-actions 적용하기 - src/modules/counter.js
// 7 - 추가내용. redux-actions 라이브러리 사용하기
import { createAction, handleActions } from 'redux-actions';
// 1. 액션 타입 정의
// 액션 타입: 대문자, 문자열 내용: '모듈 이름/액션 이름' => 문자열 안에 모듈 이름을 넣어서 액션의 이름이 충돌되지 않게 함
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
// 2. 액션 생성 함수 만들기
// redux-actions 라이브러리 中 createAction 사용한 경우
export const increase = createAction(INCREASE); // createAction: 매번 객체를 직접 만들어 줄 필요 없이 더욱 간단하게 액션 함수 선언 가능
export const decrease = createAction(DECREASE);
// 아래는 redux-actions 라이브러리를 사용하지 않았을 때의 코드임
// 앞 부분에 export를 붙혀서 추후 이 함수를 다른 파일에서 불러와 사용할 수 있음
// export const increase = () => ({ type: INCREASE }); // export: 여러 개를 내보낼 수 있음, export default: 한 개만 내보낼 수 있음
// export const decrease = () => ({ type: DECREASE });
// 3. 초기 상태 및 리듀서 함수 만들기
// 3-1. 초기 상태 만들기 - number 값을 설정해줌
const initialState = {
number: 0,
};
// 3-2. 리듀서 함수 만들기 - 현재 상태를 참조하여 새로운 객체를 생성해서 반환하는 코드
// redux-actions 라이브러리 中 리듀서 함수도 더 간단하게 만들기
const counter = handleActions(
// handleActions 함수 - 첫 번째 파라미터: 각 액션에 대한 업데이트 함수, 두 번째 파라미터: 초기 상태
// 가독성이 올라감
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 }),
},
initialState,
);
// 아래는 redux-actions 라이브러리를 사용하지 않았을 때의 코드임
// function counter(state = initialState, action) {
// switch (action.type) {
// case INCREASE:
// return {
// number: state.number + 1,
// };
// case DECREASE:
// return {
// number: state.number - 1,
// };
// default:
// return state;
// }
// }
export default counter; // export default: 한 개만 내보낼 수 있음, export: 여러 개를 내보낼 수 있음
// export default 불러오는 방식
// import counter from './counter';
// export 불러오는 방식
// import { increase, decrease }, './counter';
1-2. todos모듈에 적용
todos 모듈에도 아래의 2가지를 변경해 적용합니다.
1. 액션 생성 함수 -> createAction 함수(각 액션 생성 함수에서 파라미터를 필요로 함)
+ createAction으로 액션을 만들면 액션에 필요한 추가 데이터는 payload라는 이름을 사용합니다.
// 1. createAction으로 액션을 만든 경우 액션에 필요한 추가 데이터를 사용하는 방법(payload)
const MY_ACTION = 'sample/MY_ACTION';
const myAction = createAction(MY_ACTION);
const action = myAction('hello world');
/*
결과:
{ type: MY_ACTION, payload: 'hello world'}
*/
/*
2. 액션 생성 함수에서 받아 온 파라미터를 그대로 payload에 넣는 것이 아니라 변형을 주어 넣고 싶다면,
createAction의 두 번째 함수에 payload를 정의하는 함수를 따로 선언해서 넣어주면 됨
*/
const MY_ACTION = 'sample/MY_ACTION';
const myAction = createAction(MY_ACTION, text => `${text}!`);
const action = myAction('hello world');
/*
결과:
{ type: MY_ACTION, payload: 'hello world!'}
*/
2. 리듀서 함수 -> handleActions 함수
- todos모듈에 redux-actions 적용하기 - src/modules/todos.js
// 7 - 추가내용. redux-actions 라이브러리 사용하기
import { createAction, handleActions } from 'redux-actions';
// 1. 액션 타입 정의하기
const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제그함
// 2. 액션 생성 함수 만들기
// redux-actions 라이브러리 中 createAction 사용한 경우
export const changeInput = createAction(CHANGE_INPUT, (input) => input);
// 아래는 redux-actions 라이브러리를 사용하지 않았을 때의 코드임
// ..생략
let id = 3; // id: 각 todo 객체가 갖고 있게 될 고윳값(3인 이유: 초기 상태 작성 시 todo 객체 두 개를 사전에 넣을 것이기 때문)
// insert가 호출될 때마다 id가 1씩 더해집니다.
// 추가(insert)의 경우 todo 객체를 액션 객체 안에 넣어 주어야 하기 때문에 두 번째 파라미터에 text를 넣으면 todo 객체가 반환되는 함수를 넣어줌
export const insert = createAction(INSERT, (text) => ({
id: id++,
text,
done: false,
}));
// 파라미터를 그대로 반환하는 함수(id=>id를 생략해도 똑같이 반환하지만 어떤 값이 파라미터로 필요한지 직관적일 수 있게 써줌)
export const toggle = createAction(TOGGLE, (id) => id);
export const remove = createAction(REMOVE, (id) => id);
// 3. 초기 상태 및 리듀서 함수 만들기
// 객체에 한 개 이상의 값이 들어가므로 불변성을 유지해줘야 함(spread 연산자 사용)
// 배열에 변화를 줄 때는 배열 내장 함수를 사용하여 구현하면 됨
const initialState = {
input: '',
todos: [
{
id: 1,
text: '리덕스 기초 배우기',
done: true,
},
{
id: 2,
text: '리액트와 리덕스 사용하기',
done: false,
},
],
};
// redux-actions 라이브러리 中 handleActions 함수로 리듀서 재작성
const todos = handleActions(
{
// 아래에서 쓰이는 payload: createAction으로 액션 생성 함수를 만든 경우 파라미터로 받은 값을 다 action.payload로 사용함
// payload 객체 비구조화 할당/일반 action.payload 적용 코드가 한 줄씩 적혀있음
[CHANGE_INPUT]: (state, { payload: input }) => ({ ...state, input }), // payload의 이름을 잘 구분하기 위해 객체 비구조화 할당 적용
// [CHANGE_INPUT]: (state, action) => ({ ...state, input: action.payload }),
[INSERT]: (state, { payload: todo }) => ({
...state,
todos: state.todos.concat(todo),
}),
// [INSERT]: (state, action) => ({
// ...state,
// todos: state.todos.concat(action.payload), // concat: 인자로 주어진 배열이나 값들을 기존 배열에 합쳐서 새 배열을 반환
// }),
[TOGGLE]: (state, { payload: id }) => ({
...state,
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo,
),
}),
// [TOGGLE]: (state, action) => ({
// ...state,
// todos: state.todos.map((todo) =>
// todo.id === action.payload ? { ...todo, done: !todo.done } : todo,
// ),
// }),
[REMOVE]: (state, { payload: id }) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== id),
}),
// [REMOVE]: (state, action) => ({
// ...state,
// todos: state.todos.filter((todo) => todo.id !== action.payload),
// }),
},
initialState,
);
// 아래는 redux-actions 라이브러리를 사용하지 않았을 때의 코드임
// ..생략
export default todos;
2. immer
리듀서에서 상태를 업데이트할 때는 불변성을 지켜야 하기 때문에 앞에서는 spread 연산자(...)와 배열의 내장 함수를 활용했습니다. 객체의 구조가 복잡해지거나 객체로 이루어진 배열을 다룰 경우 immer을 사용하면 훨씬 편리하게 상태를 관리할 수 있습니다.
앞에서 immer의 개념에 대해서 한 번 다뤘었습니다. (여기 클릭)
- redux-actions 라이브러리 설치
yarn add immer
- todos모듈에 immer 라이브러리 적용하기 - src/modules/todos.js
// 7 - 추가내용. redux-actions 라이브러리 사용하기
import { createAction, handleActions } from 'redux-actions';
import produce from 'immer';
// 1. 액션 타입 정의하기
const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제그함
// 2. 액션 생성 함수 만들기
// redux-actions 라이브러리 中 createAction 사용한 경우
export const changeInput = createAction(CHANGE_INPUT, (input) => input);
// 아래는 redux-actions 라이브러리를 사용하지 않았을 때의 코드임
// export const changeInput = (input) => ({
// type: CHANGE_INPUT,
// input,
// });
let id = 3; // id: 각 todo 객체가 갖고 있게 될 고윳값(3인 이유: 초기 상태 작성 시 todo 객체 두 개를 사전에 넣을 것이기 때문)
// insert가 호출될 때마다 id가 1씩 더해집니다.
// 추가(insert)의 경우 todo 객체를 액션 객체 안에 넣어 주어야 하기 때문에 두 번째 파라미터에 text를 넣으면 todo 객체가 반환되는 함수를 넣어줌
export const insert = createAction(INSERT, (text) => ({
id: id++,
text,
done: false,
}));
// 파라미터를 그대로 반환하는 함수(id=>id를 생략해도 똑같이 반환하지만 어떤 값이 파라미터로 필요한지 직관적일 수 있게 써줌)
export const toggle = createAction(TOGGLE, (id) => id);
export const remove = createAction(REMOVE, (id) => id);
// 3. 초기 상태 및 리듀서 함수 만들기
// 객체에 한 개 이상의 값이 들어가므로 불변성을 유지해줘야 함(spread 연산자 사용)
// 배열에 변화를 줄 때는 배열 내장 함수를 사용하여 구현하면 됨
const initialState = {
input: '',
todos: [
{
id: 1,
text: '리덕스 기초 배우기',
done: true,
},
{
id: 2,
text: '리액트와 리덕스 사용하기',
done: false,
},
],
};
// + immer 적용
// redux-actions 라이브러리 中 handleActions 함수로 리듀서 재작성
const todos = handleActions(
{
// 아래에서 쓰이는 payload: createAction으로 액션 생성 함수를 만든 경우 파라미터로 받은 값을 다 action.payload로 사용함
// immer/payload 객체 비구조화 할당 코드가 한 줄씩 적혀있음
[CHANGE_INPUT]: (state, { payload: input }) =>
produce(state, (draft) => {
draft.input = input;
}),
// [CHANGE_INPUT]: (state, { payload: input }) => ({ ...state, input }), // payload의 이름을 잘 구분하기 위해 객체 비구조화 할당 적용
[INSERT]: (state, { payload: todo }) =>
produce(state, (draft) => {
draft.todos.push(todo);
}),
// [INSERT]: (state, { payload: todo }) => ({
// ...state,
// todos: state.todos.concat(todo),
// }),
[TOGGLE]: (state, { payload: id }) =>
produce(state, (draft) => {
const todo = draft.todos.find((todo) => todo.id === id);
todo.done = !todo.done;
}),
// [TOGGLE]: (state, { payload: id }) => ({
// ...state,
// todos: state.todos.map((todo) =>
// todo.id === id ? { ...todo, done: !todo.done } : todo,
// ),
// }),
[REMOVE]: (state, { payload: id }) =>
produce(state, (draft) => {
const index = draft.todos.findIndex((todo) => todo.id === id);
draft.todos.splice(index, 1); // splice() 메서드는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경
}),
// [REMOVE]: (state, { payload: id }) => ({
// ...state,
// todos: state.todos.filter((todo) => todo.id !== id),
// }),
},
initialState,
);
export default todos;
사실 이 프로젝트는 간단하기에 immer을 사용하지 않아도 되긴 합니다..
3. Hooks
리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수 대신 react-redux에서 제공하는 Hooks를 사용해봅시다.
3-1. useSelector
useSelector
- connect 함수를 사용하지 않고도 리덕스의 상태 조회 가능
- useSelector 사용 방법
const 결과 = useSelector(상태 선택 함수);
// 상태 선택 함수는 과거에 만들었던 mapStateToProps와 형태가 동일함
// mapStateToProps 함수: 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수
const mapStateToProps = (state) => ({
// state: 현재 스토어가 지니고 있는 상태
number: state.counter.number,
});
먼저, 기존에 작성했던 코드를 봅시다.
- useSelector 사용 X, connect를 사용한 기존 코드 - src/containers/CounterContainer3.js
// 3. 더더더 간단하게 CounterContainer 만들기(CounterContainer3)
import { connect } from 'react-redux';
import Counter from '../components/Counter';
const CounterContainer3 = ({ number, increase, decrease }) => {
// mapStateToProps, mapDispatchProps에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달됨
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
export default connect(
(state) => ({
number: state.counter.number,
}),
)(CounterContainer3);
위의 코드에서, CounterContainer에 connect 함수 대신 useSelector을 사용해 counter.number 값을 조회함으로써 Counter에게 Props를 넘겨주겠습니다.
- 1. useSelector 적용 - src/containers/CounterContainerHooks.js
// useSelector Hook을 사용한 CounterContainerHooks
// useSelector: connect 함수를 사용하지 않고도 리덕스의 상태 조회 가능
import { useSelector } from 'react-redux';
import Counter from '../components/Counter';
const CounterContainer = () => {
const number = useSelector((state) => state.counter.number); // connect 함수 대신 useSelector을 사용해 counter.number 값을 조회
return <Counter number={number} />; // connect 함수 대신 useSelector을 사용해 counter.number 값을 조회함으로써 Counter에게 Props를 넘겨줌
};
export default CounterContainer;
3-2. useDispatch
useDispatch
- 컴포넌트 내부에서 스토어의 내장 함수 dispatch를 사용할 수 있게 해줌
- 컨테이너 컴포넌트에서 액션을 디스패치해야 한다면 이 Hook을 사용하면 됨
+ 디스패치: 액션을 발생시키는 것
- useDispatch 사용 방법
const dispatch = useDispatch();
dispatch({ type: 'SAMPLE_ACTION' });
CounterContainerHooks 컴포넌트에 useDispatch를 적용해 INCREASE, DECREASE 액션을 발생시켰습니다.
- 2. useDispatch 적용 - src/containers/CounterContainerHooks.js
// useSelector Hook을 사용한 CounterContainer
// useSelector: connect 함수를 사용하지 않고도 리덕스의 상태 조회 가능
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';
const CounterContainer = () => {
const number = useSelector((state) => state.counter.number); // 1. connect 함수 대신 useSelector을 사용해 counter.number 값을 조회
// 2. useDispatch를 사용해 액션 디스패치하기
const dispatch = useDispatch();
return (
<Counter
number={number}
onIncrease={() => dispatch(increase())} // 2. 숫자가 바뀌어서 컴포넌트가 리렌더링 될 때마다 onIncrease함수와 onDecrease 함수가 새롭게 만들어짐 -> 컴포넌트 성능 최적화 필요 시 useCallback 사용
onDecrease={() => dispatch(decrease())}
/>
); // connect 함수 대신 useSelector을 사용해 counter.number 값을 조회함으로써 Counter에게 Props를 넘겨줌
};
export default CounterContainer;
3-2 useDispatch를 이용할 때는 3-3의 useCallback을 같이 이용해주면 좋습니다.
3-3. useCallback
위의 코드(3-2 src/containers/CounterContainerHooks.js)의 에서 컴포넌트 성능을 최적화해야하는 상황이라면, useCallback으로 액션을 디스패치하는 함수를 감싸주는 것이 좋습니다.
- 3. useCallback 적용 - src/containers/CounterContainerHooks.js
// useSelector Hook을 사용한 CounterContainerHooks
// useSelector: connect 함수를 사용하지 않고도 리덕스의 상태 조회 가능
import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';
const CounterContainer = () => {
const number = useSelector((state) => state.counter.number); // 1. connect 함수 대신 useSelector을 사용해 counter.number 값을 조회
// 2. useDispatch를 사용해 액션 디스패치하기
const dispatch = useDispatch();
// 3. useCallback을 사용해 액션을 디스패치하는 함수를 감싸줌
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
return (
<Counter
number={number}
// onIncrease={() => dispatch(increase())} // 2. 숫자가 바뀌어서 컴포넌트가 리렌더링 될 때마다 onIncrease함수와 onDecrease 함수가 새롭게 만들어짐 -> 컴포넌트 성능 최적화 필요 시 useCallback 사용
// onDecrease={() => dispatch(decrease())}
onIncrease={onIncrease}
onDecrease={onDecrease}
/>
); // connect 함수 대신 useSelector을 사용해 counter.number 값을 조회함으로써 Counter에게 Props를 넘겨줌
};
export default CounterContainer;
3-4. 한번 더 해보기(3.1~3.3)
3-1~3-3까지에서는 CounterContainer을 connect 함수 대신 Hooks를 사용했습니다. 이번에는 TodosContainer을 Hooks로 변환해봅시다.
먼저, 기존에 작성했던 코드를 봅시다.
- Hooks 사용 X, connect를 사용한 기존 코드 - src/containers/TodosContainer.js
import { connect } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';
// 이전에 todos 모듈에서 작성했던 액션 생성 함수와 상태 안에 있던 값을 컴포넌트의 props로 전달해주었음
const TodosContainer = ({
input,
todos,
changeInput,
insert,
toggle,
remove,
}) => {
return (
<Todos
input={input}
todos={todos}
onChangeInput={changeInput}
onInsert={insert}
onToggle={toggle}
onRemove={remove}
/>
);
};
export default connect(
// 비구조화 할당을 통해 todos를 분리하여
// state.todos.input 대신 todos.input을 사용
({ todos }) => ({
input: todos.input,
todos: todos.todos,
}),
{
// 두 번째 파라미터를 아예 객체 형태로 넣어주어 connect 함수가 내부적으로 bindActionCreators 작업을 대신 하도록 함
changeInput,
insert,
toggle,
remove,
},
)(TodosContainer);
- TodosContainer에 종합 Hooks 적용 - src/containers/TodosContainerHooks.js
import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';
const TodosContainerHooks = () => {
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 TodosContainerHooks;
3-5. useActions
useActions
- 액션 생성 함수를 액션을 디스패치하는 함수로 변환해 줌
- 액션 생성 함수를 사용하여 액션 객체를 만들고, 만든 객체를 스토어에 디스패치하는 작업을 해주는 함수를 자동으로 만들어줌
- 두 가지 파라미터를 필요로 함(첫 번째: 액션 생성 함수로 이루어진 배열, 두 번째: deps 배열 - 이 배열 안에 들어 있는 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만듦)
useActions는 react-redux에 내장된 Hook은 아닙니다. 그러나 리액트 공식 문서에서 복사해 사용할 수 있도록 하고 있습니다.
아래의 코드를 복사해 src 디렉터리 안에 lib라는 디렉터리를 생성 후 useActions 파일을 작성해줍니다. 아래의 코드를 그대로 복사 붙혀넣기 하면 됩니다.
리액트 공식문서에서 제공하는 useActions 함수 - src/lib/useActions.js
import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
import { useMemo } from 'react';
export function useActions(actions, deps) {
const dispatch = useDispatch();
return useMemo(
() => {
if (Array.isArray(actions)) {
// 첫 번째 파라미터: 액션 생성 함수로 이루어진 배열
return actions.map((a) => bindActionCreators(a, dispatch));
}
return bindActionCreators(actions, dispatch);
},
deps ? [dispatch, ...deps] : [dispatch], // 두 번째 파라미터: dispatch 배열 안에 들어 있는 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만듦
);
}
(책에는 deps?[dispatch, ...deps]:deps로 되어있는데 왜 이 사이트에 들어가면 마지막 deps가 [dispatch]로 되어있는지 모르겠습니다..)
이렇게 useActions를 만들었으면, TodoContainerHooks에서 useActions를 불러와 사용해봅시다. 아래처럼 코드를 수정합니다.
- TodosContainerHooks에 useActions 적용 - src/containers/TodosContainerHooks.js
// import { useCallback } from 'react'; -> useDispatch 사용 시 필요
import { useSelector, useDispatch } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';
import { useActions } from '../lib/useActions';
const TodosContainerHooks = () => {
const { input, todos } = useSelector(({ todos }) => ({
// 1. useSelector 할당 시 비구조화 할당을 이용함
input: todos.input,
todos: todos.todos,
}));
// // useDispatch 사용 시 각 액션을 디스패치하는 함수들을 만듦
// // 액션의 종류가 총 4가지(onChangeInput, onInsert, onToggle, onRemove)로 많은데 어떤 값이 액션 생성 함수의 파라미터로 사용되어야 하는지 일일이 명시하고 있음 -> 번거로움
// 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]);
// 위의 useDispatch의 번거로움을 해소하기 위한 코드(src/lib/useActions 파일 생성 후 작성)
const [onChangeInput, onInsert, onToggle, onRemove] = useActions(
[changeInput, insert, toggle, remove],
[],
);
return (
<Todos
input={input}
todos={todos}
onChangeInput={onChangeInput}
onInsert={onInsert}
onToggle={onToggle}
onRemove={onRemove}
/>
);
};
export default TodosContainerHooks;
지금까지 connect를 Hooks로 변경해보았는데요, connect와 Hooks를 같이 사용해도 상관 없습니다.
connect VS Hooks(useSelector) 각각으로 컨테이너를 만들었을 때의 차이점
- connect 함수: 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화됨
- Hooks(useSelector): 최적화 작업이 자동으로 이루어지지 않아 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해줘야 함
- TodosContainerHooks에 React.memo 적용 - src/containers/TodosContainerHooks.js
import React from 'react';
// ... 생략
export default React.memo(TodosContainerHooks);
// useSelector 이용 시 최적화 작업이 자동으로 이루어지지 않아 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해줘야 함
// 반면, connect 함수: 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화됨
// 지금 프로젝트에서는 TodosContainerHooks의 부모 컴포넌트인 App 컴포넌트가 리렌더링되는 일이 없으므로 불필요한 성능 최적화임
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
17. Redux 사용하기(1) - 앱 상태 관리 (0) | 2022.08.03 |
---|---|
16. Redux(리덕스) 기본 (0) | 2022.07.31 |
15. Context API (0) | 2022.07.30 |
14. 뉴스 뷰어 만들기(with. newsapi) (0) | 2022.07.27 |
13. Router(라우터) & SPA(Single Page Application) (0) | 2022.07.24 |