Web Dev/3. React 관련

React관련 싹 훑기(6) - react-redux hooks 부분 문서 읽고 실습

hYhY1234 2021. 5. 26. 14:14
728x90

Hooks 파트 문서 읽기

https://react-redux.js.org/api/hooks

 

Hooks | React Redux

Hooks

react-redux.js.org

공식문서 예시를 내가 주로 쓰는 함수형과 hooks만쓰도록 한번 만들어 보려고 한다. 

 

1. create-react-app 실행 후에 redux, react-redux 설치

- redux installation guide: https://redux.js.org/introduction/installation 

- react-redux installation guide: https://react-redux.js.org/introduction/getting-started

yarn add redux react-redux
yarn add -D redux-devtools

 

 

2. Provider API를 통해서 Redux Store를 react component에서 쓸수 있도록 한다. Redux Store에는 reducer를 넘겨주어야한다. [redux api 참고]

파일구조, redux폴더 내에 reducer와 store를 정의한다

// store.js 파일 
import { createStore } from "redux";
import rootReducer from "./reducers";

export default createStore(rootReducer);


// reducers/index.js
// reducer를 comine해준다
import { combineReducers } from "redux";
import visibilityFilter from "./visibilityFilter";
import todos from "./todos";

export default combineReducers({ todos, visibilityFilter });

 

3. redux devtools 설정

https://github.com/zalmoxisus/redux-devtools-extension#installation

 

zalmoxisus/redux-devtools-extension

Redux DevTools extension. Contribute to zalmoxisus/redux-devtools-extension development by creating an account on GitHub.

github.com

// store.js
import { createStore } from "redux";
import rootReducer from "./reducers";

export default createStore(
  rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

미들웨어를 설정해줘야 devtool을 사용할 수 있다.

4. class형 컴포넌트를 함수형으로 만들고 useDispatch쓰기

// 홈페이지 예제
import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../redux/actions";

class AddTodo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { input: "" };
  }

  updateInput = input => {
    this.setState({ input });
  };

  handleAddTodo = () => {
    this.props.addTodo(this.state.input);
    this.setState({ input: "" });
  };

  render() {
    return (
      <div>
        <input
          onChange={e => this.updateInput(e.target.value)}
          value={this.state.input}
        />
        <button className="add-todo" onClick={this.handleAddTodo}>
          Add Todo
        </button>
      </div>
    );
  }
}

export default connect(
  null,
  { addTodo }
)(AddTodo);
// export default AddTodo;

이렇게 되어있는 코드를 함수형 컴포넌트로 만들면 아래와 같다.

import { useDispatch } from "react-redux";
import { addTodo } from "../redux/actions";
import { useState } from "react";

const AddTodo = () => {
  const dispatch = useDispatch();
  const [input, setInput] = useState("");

  const increment = () => {
    dispatch(addTodo(input));
    setInput("");
  };

  return (
    <div>
      <input onChange={(e) => setInput(e.target.value)} value={input} />
      <button className="add-todo" onClick={increment}>
        Add Todo
      </button>
    </div>
  );
};

export default AddTodo;

원래 예시를 보면 connect에 mapDispatchToProps를 넘겨주고 있다. connect는 두번째 인자인 mapDispatchToProps위치에 dispatch를 넘겨주는데 이를 사용하는 함수를 원래 반환해준다. 

const mapDispatchToProps = (dispatch) => {
  return {
    // dispatching plain actions
    increment: () => dispatch({ type: 'INCREMENT' }),
    decrement: () => dispatch({ type: 'DECREMENT' }),
    reset: () => dispatch({ type: 'RESET' }),
  }
}

하지만 우리는 이제 useDispatch hooks를 통해서 dispatch를 바로 가지고 오기때문에 여기다가 action을 반환하는 함수를 넘겨주면된다. 

// class형일때
handleAddTodo = () => {
    this.props.addTodo(this.state.input); // props로 넘겨준 addTodo를 이용해서 dispatch
    this.setState({ input: "" });
};


// 함수형일때
const increment = () => {
  dispatch(addTodo(input));
  setInput("");
};

 

5. useSelector hooks 사용하기

https://react-redux.js.org/api/hooks#useselector

 

Hooks | React Redux

Hooks

react-redux.js.org

여기에 따르면 useSelector는 대략 mapStateToProps랑 비슷한데, useSelector는 컴포넌트가 rerender될때마다 불릴거다(이전 값 reference랑 비교해서 변했을때). useSelector는 action이 dispatch되었을때, reference comparison을 해서 이전값에 비교해서 변했으면 rerender가 되도록 강제한다. 

// 예제에있는 selectors.js, useSelector내에서 사용해서 data 모양을 바꾼다
import { VISIBILITY_FILTERS } from "../constants";

export const getTodosState = (state) => state.todos;

export const getTodoList = (state) =>
  getTodosState(state) ? getTodosState(state).allIds : [];

export const getTodoById = (state, id) =>
  getTodosState(state) ? { ...getTodosState(state).byIds[id], id } : {};

export const getTodos = (state) =>
  getTodoList(state).map((id) => getTodoById(state, id));

export const getTodosByVisibilityFilter = (state, visibilityFilter) => {
  const allTodos = getTodos(state);
  switch (visibilityFilter) {
    case VISIBILITY_FILTERS.COMPLETED:
      return allTodos.filter((todo) => todo.completed);
    case VISIBILITY_FILTERS.INCOMPLETE:
      return allTodos.filter((todo) => !todo.completed);
    case VISIBILITY_FILTERS.ALL:
    default:
      return allTodos;
  }
};

아래는 TodoList.js가 connect를 쓰는 것을  useSelector를 사용한것이다. 

// connect쓸때

import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
// import { getTodos } from "../redux/selectors";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { VISIBILITY_FILTERS } from "../constants";

const TodoList = ({ todos }) => (
  <ul className="todo-list">
    {todos && todos.length
      ? todos.map((todo, index) => {
          return <Todo key={`todo-${todo.id}`} todo={todo} />;
        })
      : "No todos, yay!"}
  </ul>
);

const mapStateToProps = state => {
  const { visibilityFilter } = state;
  const todos = getTodosByVisibilityFilter(state, visibilityFilter);
  return { todos };
};

export default connect(mapStateToProps)(TodoList);
// 함수형일때
import Todo from "./Todo";
import { useSelector } from "react-redux";
import { getTodosByVisibilityFilter } from "../redux/selectors";

const TodoList = () => {
  const todos = useSelector((state) =>
    getTodosByVisibilityFilter(state, state.visibilityFilter)
  );
  return (
    <ul className="todo-list">
      {todos && todos.length
        ? todos.map((todo, index) => {
            return <Todo key={`todo-${todo.id}`} todo={todo} />;
          })
        : "No todos, yay!"}
    </ul>
  );
};

export default TodoList;

 

이렇게 해서 useSelector를 사용할 수 있다. 

 

내 실습 코드

https://github.com/hayoung0Lee/react-redux-hooks

 

hayoung0Lee/react-redux-hooks

Contribute to hayoung0Lee/react-redux-hooks development by creating an account on GitHub.

github.com

 

 

실습 완료

 

후기

redux도 redux인데 역시 엄청난 사람들이 예제를 만들어놔서 그런지 상태관리를 이렇게 해야하는구나 깨달을 수 있었다. Normalizr같은걸 안쓰고도 id를 뽑아서 object를 hash로 접근할수있게 구성하는건 정말 잘 기억을 해둬야겠다.