React관련 싹 훑기(6) - react-redux hooks 부분 문서 읽고 실습
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 참고]

// 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로 접근할수있게 구성하는건 정말 잘 기억을 해둬야겠다.