Redux를 이용한 상태관리 실습편

개발 요모조모·2022년 4월 6일
0

redux

목록 보기
2/6
post-thumbnail

이번에는 react-redux에서 제공하는 hooks를 이용해 상태관리를 해보고자 한다. connect함수를 사용하여 dispatch를 해주는 대신 hooks을 이용해 간편하게 상태를 조회하고 dispatch를 하고자 한다.

hooks을 통해 리덕스 적용하기

useSelector

  • 리덕스를 통해 상태 조회하기 (connect 함수 사용대신)

공식문서에서는 useSelector의 렌더링에 대해서 다음과 같이 말한다.

작업이 전달되면 useSelector()이전 선택기 결과 값과 현재 결과 값의 참조 비교를 수행합니다. 서로 다른 경우 구성 요소는 강제로 다시 렌더링됩니다. 동일하면 구성 요소가 다시 렌더링되지 않습니다.

작성은 다음과 같이 한다.


const 결과 = useSelectior(상태 선택함수)
const result: any = useSelector(selector: Function, equalityFn?: Function)

하지만 아래와 같이 조회할 경우 매번 렌더링 될 때마다 새로운 객체를 만들어서 상태가 바뀌었는지 확인이 안되어 불필요한 렌더링이 되기도 한다.
이런 경우는 여러번 useSelector를 사용하거나 React.memo를 사용해서 렌더링을 방지한다.

const { number, text } = useSelector(state => ({
  number: state.counter.number,
  text: state.counter.text
}));

useDispatch

  • 스토어의 내장 함수 dispatch를 사용할 수 있게 해준다.
    컨테이너 컴포넌트에서 액션을 디스패치할 때 사용하면 된다.
    (connect 함수 사용대신)

const dispatch = useDispatch()
dispatch({ type: 'ADD_PLAY' })

useStore

  • useStore는 컴포넌트 내부에서 스토어 객체를 직접 사용할 때 쓰는 함수이다.

const store = useStore()
store.dispatch({ type: 'ADD_PLAY' })
store.getState()



세 개의 hooks를 살펴보았는데 실습으로는 다음과 같이 보여질 수 있다.

먼저 전체적인 리덕스 모듈의 구조는 다음 파트에서 다루고 이번 파트는 hooks위주의 사용법을 보여주고자 한다.


그래서 폴더에서 hook을 사용한 containers 부분만 살펴보고자 한다.

Hooks

Containers/ CounterContainer.js
이 파일은 단순하게 값을 +1 , -1 하는 역할을 한다.


import Counter from '../components/Counter';
import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from '../modules/counter';

const CounterContainer = () => {
  const number = useSelector((state) => state.counter.number);
  //상태를 조회한다. 
  const dispatch = useDispatch();
  // dispatch라는 변수에 함수를 담아 사용한다.
  const onIncrese = useCallback(() => dispatch(increase()), [dispatch]);
  const onDecrese = useCallback(() => dispatch(decrease()), [dispatch]);
  // useCallback 은 재사용을 통한 성능 최적화때문에 사용하는데 
  // 다른 파트에서 더 다뤄보고자 한다. 
  return (
    <Counter number={number} onIncrese={onIncrese} onDecrese={onDecrese} />
  );
 // useCallback를 사용하지 않을 경우는 아래와 같이 작성하면 된다. 
  return (
    <Counter 
		number={number} 
	 	onIncrese={() => dispatch(increse())}
		onDecrese={() => dispatch(decrese())} />
  );
};

export default CounterContainer;

useSelector, useDispatch를 불러오고 모듈에서도 +1, -1을 해주는 액션을 발생시켜주기 위해서 decrease와 increase를 불러온다.

만약 hooks을 사용하지 않는다면 다음과 같이 connect함수로 작성되어진다.
connect 함수는 스토어의 상태나 디스패치 함수를 props로 넣어줄수 있다.


connect 함수

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)

mapStateToProps는 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정한 함수고 mapDispatchToProps는 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수이다.


import Counter from '../components/Counter';
import { connect } from 'react-redux';
import { decrease, increase } from '../modules/counter';

const CounterContainer = ({number, increase, decrease}) => {
  return (
    <Counter number={number} onIncrese={onIncrese} onDecrese={onDecrese} />
  );
};


const mapStateToProps = state => ({
  number: state.counter.number
})
const mapDispatchToProps = dispatch => ({
  increase: () => {
    dispatch(increase())
  },
  decrease: () => {
    dispatch(decrease())
  }
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CounterContainer)

아래와 같이도 작성할 수 있다.
파라미터를 함수 형태가 아닌 액션 생성 함수로 이루어진 객체 형태로 넣어줘도 된다.


import Counter from '../components/Counter';
import { connect } from 'react-redux';
import { decrease, increase } from '../modules/counter';

const CounterContainer = ({ number, increase, decrease }) => {
  return <Counter number={number} onIncrese={increase} onDecrese={decrease} />;
};

export default connect(
  (state) => ({
    number: state.counter.number,
  }),
  {
    increase,
    decrease,
  },
)(CounterContainer);

마찬가지로 다음 파일에서도 hooks를 똑같이 적용해준다.
Containers/ TodoContainer.js


import React from 'react';
import { useCallback } from 'react';
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 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}
      onRemove={onRemove}
      onToggle={onToggle}
    />
  );
};

export default React.memo(TodosContainer);

useAction 유틸 함수 만들어서 사용하기

작성한 useAction 함수는 (액션 생성 함수) -> (액션을 디스패치하는 함수)로 변환해준다.
액션 생성 함수 - 액션 객체 생성, 생성된 객체를 스토어에 디스패치하도록 만들어 준다.
lib 폴더 안에 useAction.js를 다음과 같이 작성해준다.

공식문서 참고
https://react-redux.js.org/api/hooks#recipe-useactions


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] : deps,
  );
}

useActions(actions, deps)
첫 번째 인자 actions은 액션 생성함수로 이루어진 배열, 두 번째 인자 deps는 배열로 배열 안 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만든다.

useActions 함수를 통해 Containers/ TodoContainer.js에서는 다음과 같이 따로 dispatch함수를 불러오지 않고 간편하게 작성할 수 있다.


import React from 'react';
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 TodosContainer = () => {
  const { input, todos } = useSelector(({ todos }) => ({
    input: todos.input,
    todos: todos.todos,
  }));

  const [onChangeInput, onInsert, onRemove, onToggle] = useActions(
    [changeInput, insert, remove, toggle],
    [],
  );

connect 와 useSelector 차이점

  • connect 함수를 사용해 컨테이너를 만들 경우, 해당 컨테이너의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화 된다.

  • useSelector의 경우, 성능 최적화가 안되므로 React.memo를 컴포넌트에 사용하는 것이 좋다.

다음 파트에서는 전체적인 리덕스 모듈을 어떻게 작성해야 하는지에 대해 살펴보고자 한다.

발췌: 리액트를 다루는 기술, 김민준
리덕스 공식문서: https://react-redux.js.org/introduction/getting-started

profile
꾸준한 삽질과 가끔의 성취, 개발 그 사이에서

0개의 댓글