1. Hooks를 사용하여 컨테이너 컴포넌트 만들기
리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 떄 connect 함수 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있습니다.
1.1 useSelector로 상태 조회
useSelector Hook를 사용할 경우 connect 함수를 사용하지 않고 리덕스의 상태를 조회할 수 있습니다.
useSelector 사용법은 아래와 같습니다.
const result = useSelector(상태 선택 함수);
여기서 싱태 선택함수란 mapStateToProps와 형태가 동일합니다.
이번에는 CounterContainer에 connect를 들여내고 useSelector Hooks를 사용하여 counter.number 값을 조회하도록
변경해 보겠습니다.
- containers/CounterContainer.js
import React from "react";
import { useSelector } from "react-redux";
import Counter from "../components/Counter";
import { decrease, increase } from "../modules/counter";
const CounterContainer = () => {
// state 상태 조회
const number = useSelector(state => state.counter.number);
return (
<Counter number={number} />
);
};
// 소스 최적화
export default CounterContainer;
1.2 useDispatch를 사용한 액션 디스패치
이번에는 useDispatch Hook을 사용해 보겠습니다.
이 Hook은 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용할 수 있게 해주는 Hook입니다.
사용법은 아래와 같습니다.
const dispatch = useDispath(); //Dispatch 선언
dispatch({ type: 'ACTION' }); // 디스패치 할 액션 타입 넣기
이제 CounterContainer에서 INCREASE, DECREASE 액션을 발생시키는 디스패치를 추가 해보겠습니다.
- containers/CounterContainer.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { decrease, increase } from "../modules/counter";
const CounterContainer = () => {
// state 상태 조회
const number = useSelector(state => state.counter.number);
// Dispatch 사용을 위한 useDispatch Hook 선언
const dispatch = useDispatch();
return (
<Counter
number={number}
onIncrease={() => dispatch(increase())}
onDecrease={() => dispatch(decrease())}
/>
);
};
export default CounterContainer;
useDispatch를 소스에 적용했으면 이제 프로젝트에서 +1,-1 버튼이 작동하는지 확인해 보세요.
지금은 컴포넌트가 리렌더링될 때마다 onIncrease, onDecrease 함수를 새로 만들고 있습니다.
컴포넌트 성능을 최적화해야하는 상황이 오게되면 useCallback Hook으로 액션 디스패치하는 함수를 감싸주어야 합니다..
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { decrease, increase } from "../modules/counter";
const CounterContainer = () => {
// state 상태 조회
const number = useSelector(state => state.counter.number);
// Dispatch 사용을 위한 useDispatch Hook 선언
const dispatch = useDispatch();
// useCallback 최적화
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDncrease = useCallback(() => dispatch(decrease()), [dispatch]);
return (
<Counter
number={number}
onIncrease={onIncrease}
onDecrease={onDncrease}
/>
);
};
export default CounterContainer;
위처럼 useDispatch를 사용할때는 useCallback을 함께 사용하여 최적화 할 수 있도록 습관 들이는 것이 좋습니다.
1.3 useStore를 사용하여 리덕스 스토어 사용
useStore Hook를 사용하면 컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할 수 있습니다.
사용법은 아래와 같습니다.
const store = useStore();
store.dispatch({ type: 'ACTION '});
store.getState();
useStore는 컴포넌트에서 스토어에서 직접 접근해야하는 상황에만 사용합니다.
다만 직접 사용해야 하는 상황은 흔지 않습니다.
1.4 TodosContainer Hooks 전환
이제 TodosContainer를 connect 함수 대신 useSelector와 useDispatch Hooks로 변경 해보겠습니다.
- containers/TodosContainer.js
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeInput, insert, toggle, remove } from '../modules/todos'
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;
이번에는 useSelector를 사용할 떄 비구조와 할당 문법을 적용 하였습니다.
1.5 useActions 유틸 Hook 만들어서 사용
react-redux 안에 내장되어 배포될 예정이었으나 리덕스 개발팀에서 제외한 Hook입니다.
useActions는 여러개의 액션을 사용해야 하는 경우 코드를 깔끔하게 정리하여 작성할 수 있습니다.
src 디렉토리에 lib 디렉토리를 생성하고 그안에 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
);
}
useActions Hook은 액션 생성 함수를 액션을 디스패치하는 함수로 변환해주는 역할을 합니다;
액션생성 함수를 객체로 만들고, 스토어에 디스패치하는 작업을 해주는 함수 -> 자동으로 생성하여 만들어 줍니다.
useActions Hook는 두가지 파라미터가 필요합니다.
- 액션 생성 함수로 이루어진 배열
- deps 배열(배열 안에 원소 값이 바뀌면 액션을 디스패치하는 함수를 재 생성)
- containers/TodosContainer.js
import React from "react";
import { useSelector } from "react-redux";
import { changeInput, insert, toggle, remove } from '../modules/todos'
import Todos from "../components/Todos";
import useActions from "../lib/useActions";
const TodosContainer = () => {
const { input, todos } = useSelector(({ todos }) => ({
input: todos.input,
todos: todos.todos
}));
// useActions 적용
const [onChangeInput, onInsert, onToggle, onRemove] = useActions(
[changeInput, insert, toggle, remove],
[]
);
(...)
};
export default TodosContainer;
useActions를 컨테이너에 적용해보시고 프로젝트 실행 시 기능이 동일하게 동작하는지 확인해 보세요.
1.6 connect 함수와 Hooks 차이점
컨테이너 컴포넌트를 만들떄는 connect 함수를 사용해도 되고, Hooks를 사용하셔도 됩니다.
본인이 더 편한 방식을 사용하셔도 되지만 한가지 차이점이 있습니다.
- connect 함수를 사용하여 만들면 부모 컴포넌트가 리렌더링될 경우 props값이 안바뀌었으면 리렌더링을 자동으로 방지합니다 (최적화)
- useSeletor을 사용하여 리덕스 상태를 조회했을 떄는 최적화 작업이 자동으로 이루어지지 않아서 React.memo를 컴포넌트에 추기해야 합니다.
2. git 최종 결과
'프론트엔드 > React' 카테고리의 다른 글
[프론트엔드] React 동작 구조 (가상 DOM) (0) | 2022.04.18 |
---|---|
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[1] (0) | 2022.04.12 |
[프론트엔드] 리덕스를 사용하여 리액트 어플리케이션 상태 관리하기[8] (0) | 2022.04.10 |
[프론트엔드] 리덕스를 사용하여 리액트 어플리케이션 상태 관리하기[7] (0) | 2022.04.10 |
[프론트엔드] 리덕스를 사용하여 리액트 어플리케이션 상태 관리하기[6] (0) | 2022.04.10 |