[Redux] Redux, React Redux, Redux DevTools

2023. 10. 3. 21:49React

728x90
반응형

1. Redux

📄 Redux
📄 React Redux

1-1. Installation

npm install redux
npm install react-redux

1-2. Setting

1) <Provider />

  • Redux Store를 이용 가능하게 해주는 컴포넌트인 <Provider />로 index.js의 <App/ >을 감싼다.
  • <Provider />는 react-redux에서 가져온다.
  • store는 별도의 파일에서 만들어서 가져온다.
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";

~~~ 생략 ~~~

root.render(
      <Provider store={store}>
        <App />
      </Provider>
);

~~~ 생략 ~~~

2) reducer 생성

  • store를 만들기 위해서는 reducer가 필요하므로, reducer를 먼저 만들어주자!
  • src 폴더에 /redux/reducer/reducer.js/redux/store.js를 새로 만든다.

/redux/reducer/reducer.js

let initialState = {
  count: 0,
};

function reducer(state = initialState, action) {}

export default reducer;

3) store 생성

/src/redux/store.js

import { createStore } from "redux";
import reducer from "./reducer/reducer";

let store = createStore(reducer);

export default store;

4) reducer 여러개 만들기

기능별로 reducer를 따로 만들었다면, combineReducers를 이용해 하나로 묶어줘야 한다.

/reducer/index.js

import { combineReducers } from "redux";
import authenticateReducer from "./authenticateReducer";
import productReducer from "./productReducer";

export default combineReducers({
  auth: authenticateReducer,
  product: productReducer,
});

store.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer";
// reducer를 묶어준 파일명이 index.js이기 때문에 /reducer까지만 작성해도 자동으로 index.js를 가져온다.
// 이름은 rootReducer가 아니어도 상관 없음!

let store = createStore(rootReducer, applyMiddleware(thunk));

export default store;

이렇게 변경하고 나면 useSelector로 state를 불러올 때 combineReducers에서 지정해둔 키 이름으로 접근해야 한다.

const productList = useSelector((state) => state.product.productList);

1-3. 적용하기

1) state 변경하기

  • action을 전달하는 hook인 useDispatch()를 이용한다.
  • type과 payload로 이루어진 객체를 인자로 넣는다.
    (payload는 필요한 정보를 담아서 보낼 수 있는 선택값!)
  • useDispatch를 사용하면 reducer에서 받을 수 있다.
~~~ 생략 ~~~

import { useDispatch } from "react-redux";

const ReduxPage = () => {
  const dispatch = useDispatch();

  const increase = () => {
    dispatch({ type: "INCREASE" });
  };

~~~ 생략 ~~~
  • reducer가 새로운 값을 반환하면 반환된 값으로 state가 변경된다.
  • 새로운 객체(주소값)을 return해야 한다는 것에 주의하자!

/redux/reducer/reducer.js

let initialState = {
  count: 0,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "INCREASE":
      return { ...state, count: state.count + 1 };
    default:
      return { ...state };
  }
}

export default reducer;

2) State 가져오기

  • store의 state를 가져오는 hook인 useSelector()를 이용한다.
import { useSelector } from "react-redux";

const count = useSelector((state) => state.count);

3) 컴포넌트와 state 연결하기 (mapStateToProps)

  • connect를 이용하면 state의 값을 컴포넌트의 props와 병합해서 사용할 수 있다.
  • mapStateToProps는 컴포넌트가 스토어에서 필요한 데이터만 선택할 수 있게 해준다.
  • mapState라고도 부른다.
  • mapStateToProps 함수는 Redux Store가 업데이트 될 때마다 호출된다.
  • 컴포넌트가 필요로 하는 데이터를 객체로 반환해야 한다.
  • state와 ownProps(optional)를 파라미터로 받는다.
  • 파라미터로 state만 받도록 선언하면 Store 상태가 변경될 때마다 호출된다.
  • 파라미터로 두 개의 파라미터를 받도록 선언하면, Store 상태가 변경되거나 래퍼 컴포넌트가 새 props를 받을 때마다 호출된다.
  • 반환되는 값이 변경되면 컴포넌트가 리렌더된다.
function mapStateToProps(state) {
  const { todos } = state
  return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)

4) 컴포넌트와 dispatch 연결하기 (mapDispatchToProps)

  • connect의 두 번째 매개변수로 mapDispatchToProps를 전달하면 dispatch 하는 함수를 생성하고, 그 함수들을 컴포넌트의 props로 전달할 수 있다.
  • store.dispatch를 호출하는 것이 가능하기는 하지만 React Redux에서는 컴포넌트가 Store와 직접적으로 소통하는 것을 피하고, connect를 통해 컴포넌트의 props로 연결하는 패턴을 주로 사용한다.
  • connect의 두 번째 인자를 지정하지 않으면, 컴포넌트는 기본적으로 dispatch를 받는다.
  • mapDispatchToProps(connect의 두 번째 인자)를 사용하면, props에 dispatch가 제공되지 않는다.
  • mapDispatchToProps를 제공하면 컴포넌트가 어떤 액션을 dispatch하는지 지정할 수 있어 더 선언적이다.

두 가지 형태(함수 형태와 객체 형태)가 있다.
공식 문서에서는 객체 형태를 권장한다.

4-1) 함수 형태

  • 더 많은 커스터마이징이 가능하다.
  • 첫 번째 매개변수로 dispatch를 전달받는다.
const mapDispatchToProps = (dispatch) => {
  return {
    // dispatching plain actions
    increment: () => dispatch({ type: 'INCREMENT' }),
    decrement: () => dispatch({ type: 'DECREMENT' }),
    reset: () => dispatch({ type: 'RESET' }),
  }
}
  • 매개변수를 두 개를 받는다면(두 번째는 optional) 두 번째 매개변수로는 props를 전달받게 되고, 컴포넌트가 새로운 props를 받을 때마다 재호출되어 액션을 dispatch할 함수들을 최신 props를 바탕으로 새로 바인딩해준다.
render() {
  return <button onClick={() => this.props.toggleTodo()} />
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    toggleTodo: () => dispatch(toggleTodo(ownProps.todoId))
  }
}

bindActionCreators

  • 함수 래핑을 단순화하기 위한 함수이다.
  • 각 함수를 dispatch로 자동으로 래핑해준다.
  • Redux를 모르는 컴포넌트에 액션 크리에이터를 전달하고 싶을 때 쓰면 된다.
import { bindActionCreators } from 'redux'

const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })

// binding an action creator
// returns (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)

// binding an object full of action creators
const boundActionCreators = bindActionCreators(
  { increment, decrement, reset },
  dispatch
)
// returns
// {
//   increment: (...args) => dispatch(increment(...args)),
//   decrement: (...args) => dispatch(decrement(...args)),
//   reset: (...args) => dispatch(reset(...args)),
// }
  • mapDispatchToProps와 함께 쓴 예시
import { bindActionCreators } from 'redux'
// ...

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ increment, decrement, reset }, dispatch)
}

// component receives props.increment, props.decrement, props.reset
connect(null, mapDispatchToProps)(Counter)

4-2) 객체 형태

  • 더 선언적이고 쉽다.
const mapDispatchToProps = {
  increment,
  decrement,
  reset,
}

2. Redux Middleware (redux-thunk)

📄 Redux Thunk

Redux는 동기적인 업데이트만 가능하다.
비동기 작업이 필요한 API 요청 등은 Redux로 할 수 없다...

그래서 action이 reducer로 전달되기 전에 중간에 가로채서 비동기 작업을 처리하는 Middleware를 사용한다.

Redux의 Middleware에는 redux-saga와 redux-thunk가 있다.
redux-thunk를 사용해보자!!

2-1. Installation

npm install redux-thunk

2-2. Setting

  • applyMiddleware와 thunk를 가져와서 createStore의 인자로 전달한다.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import productReducer from "./reducer/productReducer";

let store = createStore(productReducer, applyMiddleware(thunk));

export default store;

2-3. 적용하기

react-thunk를 사용하지 않던 기존에는 (동기 방식) dispatch의 인자로 action과 payload를 바로 전달했다.

비동기 작업을 하려면 우선 해당 비동기 작업을 반환하는 미들웨어 함수를 만들어둔다.
이 비동기 작업 로직 안에서 기존 방식대로 dispatch를 호출한다.

function getProducts(searchQuery) {
  return async (dispatch, getState) => {
    let url = `https:...`;
    let response = await fetch(url);
    let data = await response.json();
    dispatch({ type: "GET_PRODUCT_SUCCESS", payload: { data } });
  };
}

export const productAction = { getProducts };

이 미들웨어 함수를 호출할 때는 dispatch를 사용하는 것은 동일하지만 action과 payload가 아닌, 이 미들웨어 함수를 인자로 넣어주면 된다.

import React from "react";
import { productAction } from ".../redux/action/productAction";
import { useDispatch, useSelector } from "react-redux";

const ProductAll = () => {
  const dispatch = useDispatch();
  const getProducts = () => {
    dispatch(productAction.getProducts("searchQuery"));
  };
  getProducts();

  return <div></div>;
};

export default ProductAll;

reducer는 기존과 동일하다.

3. Redux DevTools

📄 Redux DevTools

state의 변화를 편리하게 확인할 수 있게 도와주는 도구이다.

3-1. Installation

🔗 크롬 확장 설치

npm install --save redux-devtools-extension

3-2. Setting

composeWithDevTools로 applyMiddleware를 감싸준다.

store.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer";
import { composeWithDevTools } from "redux-devtools-extension";

let store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

export default store;

3-3. 활용

개발자 도구의 Redux 탭에서 state 변화를 확인할 수 있다.

728x90
반응형

'React' 카테고리의 다른 글

[React] useRef, forwardRef, useImperativeHandle  (1) 2023.12.12
[React] Router (version 6)  (0) 2023.10.02
[React] Class Component  (0) 2023.10.02