안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
리덕스를 사용하여 리액트 애플리케이션 상태 관리하기
Hooks를 사용하여 컨테이너 컴포넌트 만들기
리덕스 스토어와 연동된 컨테이너 컴포넌트 생성 시, connect 대신 react-redux에서 제공하는 Hook을 이용할 수 있습니다.
useSelector로 상태 조회하기
useSelector Hook은 connect 함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있습니다.
(connect 함수 - 컴포넌트를 리덕스와 연결시켜주는 역할)
예시
const 결과 = useSelector(상태 선택 함수);
상태 선택 함수는 mapStateToProps와 형태가 같습니다.
CounterContainer에서 useSelector를 이용해 counter.number 값을 조회함으로써 Counter에게 props로 넘겨주겠습니다.
containers/CounterContainer.js
import { useSelector } from 'react-redux';
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
const CounterContainer = () => {
const number = useSelector(state => state.counter.number);
return (
<Counter number={number} />
);
};
export default CounterContainer;
코드가 간결해진 것을 확인할 수 있습니다.
useDispatch를 사용하여 액션 디스패치하기
useDispatch Hook은 컴포넌트 내부에서 스토어의 내장 함수 dispatch를 사용할 수 있게 해줍니다.
예시
const dispatch = useDispatch();
dispatch({ type: 'SAMPLE_ACTION' });
CounterContainer에서 useDispatch Hook을 이용해 액션을 발생시켜 보겠습니다.
containers/CounterContainer.js
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);
const dispatch = useDispatch();
return (
<Counter
number={number}
onIncrease={() => dispatch(increase())}
onDecrease={() => dispatch(decrease())}
/>
);
};
export default CounterContainer;
잘 작동하는 것을 보니 액션이 정상적으로 발생되었다는 것을 알 수 있습니다.
하지만 이 경우 숫자가 바뀌어서 리렌더링될 때마다 onIncrease 함수와 onDecrease 함수가 새롭게 생성되고 있습니다.
따라서 useCallback으로 컴포넌트 성능을 최적화 시켜주는 것이 좋습니다.
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);
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 CounterContainer;
useDispatch는 useCallback 함수와 같이 사용하는 것을 권장!
useStore를 사용하여 리덕스 스토어 사용하기
useStore Hook은 컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할 수 있습니다.
예시
const store = useStore();
store.dispatch({ type: 'SAMPLE_ACTION' });
store.getStore();
해당 Hook은 스토어에 직접 접근해야 하는 상황에만 사용하며, 이 경우는 흔치 않다고 합니다.
TodosContainer를 Hooks로 전환하기
TodoContainer를 useSelector와 useDispatch Hooks를 사용하는 형태로 변경해보겠습니다.
containers/TodoContainer.js
import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import Todos from "../components/Todos";
import { changeInput, insert, toggle, remove } from "../modules/todos";
const TodoContainer = () => {
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 TodoContainer;
useDispatch를 이용해 액션을 디스패치할 때 일일이 명시해줘야 하므로 번거로울 수도 있습니다.
useActions 유틸 Hook을 만들어서 사용하기
useActions는 원래 react-redux에 내장되려고 하였다가 꼭 필요하지 않다 판단되어 제외된 Hook이라고 합니다.
useActions를 사용하면, 여러 개의 액션을 사용해야 하는 경우 코드를 훨씬 깔끔하게 정리하여 작성할 수 있습니다.
src/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 Hook은 액션 생성 함수를 액션을 디스패치하는 함수로 변환해줍니다.
즉, 액션 생성 함수를 사용해 액션 객체를 만들고, 이를 스토어에 디스패치하는 작업을 해주는 함수를 자동으로 만들어줌!
useActions의 첫 번째 파라미터는 액션 생성 함수로 이루어진 배열
두 번째 파라미터는 deps 배열입니다. (해당 배열 안에 들어 있는 원소가 바뀌면 액션을 디스패치하는 함수를 재생성)
containers/TodoContainer.js
import { useSelector } from "react-redux";
import Todos from "../components/Todos";
import { changeInput, insert, toggle, remove } from "../modules/todos";
import useActions from "../lib/useActions";
const TodoContainer = () => {
const { input, todos } = useSelector(({ todos }) => ({
input: todos.input,
todos: todos.todos
}));
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 TodoContainer;
첫 번째 파라미터에 액션 생성 함수로 이루어진 배열을 넣어주었고, 두 번째 파라미터는 빈 배열을 넣어주었습니다.
이를 받아 스토어에 디스패치하는 작업을 해주는 함수를 만들어 반환해주었고, 이를 적용하였습니다.
위에서의 문제점을 말끔하게 해결할 수 있습니다.
connect 함수와의 주요 차이점
① connect 함수를 사용해 컨테이너 컴포넌트를 만들었을 경우
- 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화됨
② useSelector를 사용해 리덕스 상태를 조회했을 경우
- 위 최적화 작업이 자동으로 이루어지지 않으므로 React.memo를 컨테이너 컴포넌트에 사용해줘야 함
containers/TodosContainer.js
import { useSelector } from "react-redux";
import Todos from "../components/Todos";
import { changeInput, insert, toggle, remove } from "../modules/todos";
import useActions from "../lib/useActions";
import React from "react";
...
...
export default React.memo(TodoContainer);
현재 같은 경우는 App 컴포넌트가 리렌더링되는 일이 없으므로 필요하지 않습니다.
'JavaScript > React' 카테고리의 다른 글
React) 리덕스 미들웨어를 통한 비동기 작업 관리 - 미들웨어란? (0) | 2022.12.19 |
---|---|
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ③ - 리덕스 더 편하게 사용하기 (0) | 2022.12.16 |
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ② - 리액트 애플리케이션에 리덕스 적용하기 (0) | 2022.12.15 |
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ① - 준비 과정 (0) | 2022.12.09 |
React) 리덕스 라이브러리 이해하기 - 리덕스의 세 가지 규칙 (2) | 2022.11.29 |