일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Java
- 오라클
- oracle
- 예외처리
- 자바
- 대덕인재개발원
- 제네릭
- 예외미루기
- 메소드오버로딩
- 집합_SET
- 인터페이스
- EnhancedFor
- 객체 비교
- 다형성
- 정수형타입
- 어윈 사용법
- exception
- GRANT VIEW
- 자동차수리시스템
- 사용자예외클래스생성
- 생성자오버로드
- abstract
- 한국건설관리시스템
- NestedFor
- 환경설정
- 컬렉션프레임워크
- cursor문
- 추상메서드
- 컬렉션 타입
- 참조형변수
- Today
- Total
거니의 velog
(10) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 5 본문
7. Hooks를 사용하여 컨테이너 컴포넌트 만들기
* 리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수를 사용하는 대신 react-redux 에서 제공하는 Hooks를 사용할 수도 있다.
(1) useSelector로 상태 조회하기
* useSelector Hook을 사용하면 connect 함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있다. useSelector의 사용법은 다음과 같다.
const 결과 = useSelector(상태 선택 함수);
* 여기서 상태 선택 함수는 mapStateToProps와 형태가 똑같다. 이제 CounterContainer에서 connect 함수 대신 useSelector를 사용하여 counter.number 값을 조회함으로써 Counter에게 props를 넘겨 주자.
[containers/CounterContainer.js]
import React from 'react';
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;
* 꽤 간단하다.
(2) useDispatch를 사용하여 액션 디스패치하기
* 이번에는 useDispatch라는 Hook에 대해 알아보자. 이 Hook은 컴포넌트 내부에서 스토어의 내장 함수 dispatch를 사용할 수 있게 해준다. 컨테이너 컴포넌트에서 액션을 디스패치해야 한다면 이 Hook을 사용하면 된다. 사용법은 다음과 같다.
const dispatch = useDispatch();
dispatch({ type: 'SAMPLE_ACTION' });
* 이제 CounterContainer에서도 이 Hook을 사용하여 INCREASE와 DECREASE 액션을 발생시켜 보자.
[containers/CounterContainer.js]
import React from 'react';
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;
* 이렇게 코드를 작성하고 +1과 -1 버튼을 눌러서 숫자가 잘 바뀌는지 확인해 보자.
* 지금은 숫자가 바뀌어서 컴포넌트가 리렌더링될 때마다 onIncrease 함수와 onDecrease 함수가 새롭게 만들어지고 있다.
* 만약 컴포넌트 성능을 최적화해야 하는 상황이 온다면 useCallback으로 액션을 디스패치하는 함수를 감싸 주는 것이 좋다.
* 다음과 같이 코드를 한번 수정해 보자.
[containers/CounterContainer.js]
import React, { useCallback } from 'react';
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();
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과 함께 사용하는 습관을 들일 것을 권한다.
(3) useStore를 사용하여 리덕스 스토어 사용하기
* useStore Hook을 사용하면 컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할 수 있다. 사용법은 다음과 같다.
const store = useStore();
store.dispatch({ type: 'SAMPLE_ACTION' });
store.getState();
* useStore 는 컴포넌트에서 정말 어쩌다가 스토어에 직접 접근해야 하는 상황에만 사용해야 한다. 이를 사용해야 하는 상황은 흔치 않을 것이다.
(4) TodosContainer를 Hooks로 전환하기
* 이제 TodosContainer를 connect 함수 대신에 useSelector와 useDispatch Hooks를 사용하는 형태로 전환해 보자.
[containers/ TodosContainer.js]
import React, { 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를 사용할 때 비구조화 할당 문법을 활용했다.
* 또한, useDispatch를 사용할 때 각 액션을 디스패치하는 함수를 만들었는데, 위 코드의 경우 액션의 종류가 많은데 어떤 값이 액션 생성 함수의 파라미터로 사용되어야 하는지 일일이 명시해 주어야 하므로 조금 번거롭다. 이 부분은 우선 컴포넌트가 잘 작동하는 것을 확인하고 나서 한 번 개선해 보겠다. 코드를 저장하고 TodosContainer가 잘 작동하는지 확인해 보자.
(5) useActions 유틸 Hook을 만들어서 사용하기
* useActions 는 원래 react-redux에 내장된 상태로 릴리즈될 계획이었으나 리덕스 개발 팀에서 꼭 필요하지 않다고 판단하여 제외된 Hook이다. 그 대신 공식 문서에 그대로 복사하여 사용할 수 있도록 제공하고 있다.
https://react-redux.js.org/api/hooks#recipe-useactions
* 이 Hook을 사용하면, 여러 개의 액션을 사용해야 하는 경우 코드를 훨씬 깔끔하게 정리하여 작성할 수 있다.
* 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);
},
deps ? [dispatch, ...deps] : deps,
);
}
* 방금 작성한 useActions Hook은 액션 생성 함수를 액션을 디스패치하는 함수로 변환해 준다. 액션 생성 함수를 사용하여 액션 객체를 만들고, 이를 스토어에 디스패치하는 작업을 해 주는 함수를 자동으로 만들어 주는 것이다.
* useActions는 두 가지 파라미터가 필요하다. 첫 번째 파라미터는 액션 생성 함수로 이루어진 배열이다. 두 번째 파라미터는 deps 배열이며, 이 배열 안에 들어 있는 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만들게 된다.
* 한번 TodoContainer에서 useActions를 불러와 사용해 보자.
[templates/TodoContainer.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,
}));
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 TodosContainer;
* 코드를 저장한 뒤, TodoListContainer가 잘 작동하는지 다시 확인해 보자.
(6) connect 함수와의 주요 차이점
* 앞으로 컨테이너 컴포넌트를 만들 때 connect 함수를 사용해도 좋고, useSelector와 useDispatch를 사용해도 좋다. 리덕스 관련 Hook이 있다고 해서 기존 connect 함수가 사라지는 것은 아니므로, 더 편한 것을 사용하면 된다.
* 하지만 Hooks를 사용하여 컨테이너 컴포넌트를 만들 때 잘 알아 두어야 할 차이점이 있다.
* connect 함수를 사용하여 컨테이너 컴포넌트를 만들었을 경우, 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화된다.
* 반면 useSelector를 사용하여 리덕스 상태를 조회했을 때는 이 최적화 작업이 자동으로 이루어지지 않으므로, 성능 최적화를 위해서는 React.memo를 컨테이너 컴포넌트에 사용해 주어야 한다. 다음과 같이 말이다.
[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,
}));
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 React.memo(TodosContainer);
* 물론 지금과 같은 경우에는 TodosContainer의 부모 컴포넌트인 App 컴포넌트가 리렌더링되는 일이 없으므로 불필요한 성능 최적화이다.
8. 정리
* 이 장에서는 리액트 프로젝트에 리덕스를 적용하여 사용하는 방법을 배워 보았다. 리액트 프로젝트에서 리덕스를 사용하면 업데이트에 관련된 로직을 리액트 컴포넌트에서 완벽하게 분리시킬 수 있으므로 유지 보수성이 높은 코드를 작성해 낼 수 있다. 사실 이번에 만든 프로젝트처럼 정말 작은 프로젝트에 리덕스를 적용하면 오히려 프로젝트의 복잡도가 높아질 수 있다. 하지만 규모가 큰 프로젝트에 리덕스를 적용하면 상태를 더 체계적으로 관리할 수 있고, 개발자 경험도 향상시켜 준다.
'React > React_리액트 심화' 카테고리의 다른 글
(12) 리덕스 미들웨어를 통해 비동기 작업 관리 2 (0) | 2023.12.14 |
---|---|
(11) 리덕스 미들웨어를 통해 비동기 작업 관리 1 (0) | 2023.12.13 |
(9) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 4 (0) | 2023.12.13 |
(8) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 3 (0) | 2023.12.13 |
(7) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 2 (0) | 2023.12.13 |