Study/React

17 - 추가. Redux 사용하기(1) - 앱 상태 관리

delay100 2022. 8. 10. 23:11
728x90
반응형
SMALL

안녕하세요! delay100입니다. 이번 포스팅에서는 앞에 17장에서 다뤘던 리덕스 애플리케이션을 가져와서 리덕스를 좀 더 편하게 사용하는 방법을 알아보겠습니다.

대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요! 

 리액트를 다루는 기술, 개정판 17.6장 이후 내용을 다루고 있습니다. 17장의 프로젝트에서 이어서 작업을 합니다.

이번 포스팅의 Github 링크

https://github.com/delay-100/study-react/tree/main/ch17/react-redux-tutorial2
 

GitHub - delay-100/study-react

Contribute to delay-100/study-react development by creating an account on GitHub.

github.com

 


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의 개념에 대해서 한 번 다뤘었습니다. (여기 클릭)

 

12. 불변성 유지하기(immer)

안녕하세요! delay100입니다. 이번 포스팅에서는 immer을 사용해 쉽게 불변성을 유지하는 방법에 대해 공부해봅시다. 대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주

delay100.tistory.com

 

  • 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은 아닙니다. 그러나 리액트 공식 문서에서 복사해 사용할 수 있도록 하고 있습니다.

 

Hooks | React Redux

API > Hooks: the `useSelector` and `useDispatch` hooks`

react-redux.js.org

 

아래의 코드를 복사해 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를 컨테이너 컴포넌트에 사용해줘야 함

+ React.memo

 

11. 컴포넌트 성능 최적화

안녕하세요! delay100입니다. 이번 포스팅에서는 지난 10장에서 만들었던 투두리스트의 컴포넌트들에 대해 성능을 최적화 해봅시다. 이번 학습에서는 10장 프로젝트의 코드를 그대로 가져온 후 시

delay100.tistory.com

 

  • TodosContainerHooks에 React.memo 적용 -  src/containers/TodosContainerHooks.js
import React from 'react';

// ... 생략

export default React.memo(TodosContainerHooks);
// useSelector 이용 시 최적화 작업이 자동으로 이루어지지 않아 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해줘야 함
// 반면, connect 함수: 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화됨
// 지금 프로젝트에서는 TodosContainerHooks의 부모 컴포넌트인 App 컴포넌트가 리렌더링되는 일이 없으므로 불필요한 성능 최적화임

 


읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요! 

728x90
반응형
LIST