-
React관련 싹 훑기(6) - react-redux hooks 부분 문서 읽고 실습Web Dev/3. React 관련 2021. 5. 26. 14:14728x90
Hooks 파트 문서 읽기
https://react-redux.js.org/api/hooks
공식문서 예시를 내가 주로 쓰는 함수형과 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
// 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
여기에 따르면 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
후기
redux도 redux인데 역시 엄청난 사람들이 예제를 만들어놔서 그런지 상태관리를 이렇게 해야하는구나 깨달을 수 있었다. Normalizr같은걸 안쓰고도 id를 뽑아서 object를 hash로 접근할수있게 구성하는건 정말 잘 기억을 해둬야겠다.
'Web Dev > 3. React 관련' 카테고리의 다른 글
React컴포넌트에서 HTMLElement contentEditable 속성 사용하기 (0) 2021.05.27 React 관련 싹훑기(7) - Redux 앱 구상 및 간단한 상태설계 (0) 2021.05.26 React관련 싹 훑기(5) - you might not need redux, normalizr (0) 2021.05.25 React관련 싹 훑기(4) - Redux Subscriber는 어떻게 동작하는가 (0) 2021.05.25 Next.js Link컴포넌트에 className 적용하기 (0) 2021.05.23