프론트엔드 첫걸음

[Redux Toolkit Tutorial] bindActionCreator 본문

개발 공부/React

[Redux Toolkit Tutorial] bindActionCreator

차정 2022. 9. 3. 10:54

https://youtu.be/0awA5Uw6SJE

 Redux Toolkit Tutorial 9강

 

 

//import redux from 'redux';
const redux = require('redux');
const createStore = redux.createStore;
const bindActionCreators = redux.bindActionCreators;
const combineReducers = redux.combineReducers;

//상수를 만들어 놓으면 액션 재사용시 오타 방지할 수 있다.
//액션 정의
const CAKE_ORDERED = 'CAKE_ORDERED';
const CAKE_RESTOCKED = 'CAKE_RESTOCKED';
const ICECREAM_ORDERED = 'ICECREAM_ORDERED';
const ICECREAM_RESTOCKED = 'ICECREAM_RESTOCKED';

//액션을 반환하는 함수
function orderCake() {
  return {
    type: CAKE_ORDERED,

    payload: 1,
    // quantity: 1,
    // 보내려는 추가정보에 대해 payload 속성을 사용하는 것이 리덕스 컨벤션
  };
}

function restockCake(qty = 1) {
  return {
    type: CAKE_RESTOCKED,
    payload: qty,
  };
}

function orderIceCream(qty = 1) {
  return {
    type: ICECREAM_ORDERED,
    payload: qty,
  };
}

function restockIceCream(qty = 1) {
  return {
    type: ICECREAM_RESTOCKED,
    payload: qty,
  };
}


const initialCakeState = {
  numOfCakes: 10,
};

const initialIceCreamState = {
  numOfIceCreams: 20,
};

//reducer 를 하나의 관심사에 관한 reducer로 바꿈
const cakeReducer = (state = initialCakeState, action) => {
  switch (action.type) {
    case CAKE_ORDERED:
      return {
        ...state,
        numOfCakes: state.numOfCakes - 1,
      };
    case CAKE_RESTOCKED:
      return {
        ...state,

        numOfCakes: state.numOfCakes + action.payload,
      };

    default:
      return state;
  }
};

const iceCreamReducer = (state = initialIceCreamState, action) => {
  switch (action.type) {
    case ICECREAM_ORDERED:
      return {
        ...state,
        numOfIceCreams: state.numOfIceCreams - 1,
      };
    case ICECREAM_RESTOCKED:
      return {
        ...state,
        numOfIceCreams: state.numOfIceCreams + action.payload,
      };

    default:
      return state;
  }
};


const rootReducer = combineReducers({
  cake: cakeReducer,
  iceCream: iceCreamReducer,
});

const store = createStore(rootReducer);

console.log('initial State', store.getState());

//state바뀔때마다 log찍는 함수 실행
const unsubscribe = store.subscribe(() =>
  console.log('update state', store.getState())
);

//orderCake()에서 return된 함수가 reducer로 dispatch됨
//함수로 반환하지 않으면 state가 수정된 경우 dispatch에 매개변수로 전달한 {type : CAKE_ORDERED}를 전부 수정해야함
// store.dispatch({
//     type: CAKE_ORDERED,
//     quantity: 1,
//   });
// store.dispatch(orderCake());
// store.dispatch(orderCake());
// store.dispatch(orderCake());

// store.dispatch(restockCake(3));
const actions = bindActionCreators(
  { orderCake, restockCake, orderIceCream, restockIceCream },
  store.dispatch
);

actions.orderCake();
actions.orderCake();
actions.orderCake();
actions.restockCake(3);
actions.orderIceCream();
actions.orderIceCream();
actions.restockIceCream(2);

unsubscribe();

//스토어의 변경사항을 구독취소했기 때문에 console에 기록되지 않음
store.dispatch(orderCake());
store.dispatch(orderCake());

 

 

Before

store.dispatch(orderCake());
store.dispatch(orderCake());
store.dispatch(orderCake());

store.dispatch(restockCake(3));

보통은 store 객체가 직접 dispatch를 호출한다.

 

 

After

bindActionCreator 
- 액션생성함수들을 dispatch와 묶어주어 dispatch를 쉽게 한다.
값이 액션생성함수인 객체와 dispatch를 인수로 받아서 ,
액션생성함수를 호출할수 있는 bindActionCreator를 반환한다.

const bindActionCreators = redux.bindActionCreators;

const actions = bindActionCreators(
  {
    orderCake: orderCake,
    restockCake: restockCake,
    orderIceCream: orderIceCream,
    restockIceCream: restockIceCream,
  },
  store.dispatch
);

actions.orderCake();
actions.orderCake();
actions.orderCake();
actions.restockCake(3);
actions.orderIceCream();
actions.orderIceCream();
actions.restockIceCream(2);

 

bindActionCreator로 dispatch를 포장하게 되면 react에서 하위 컴포넌트에 bindActionCreator를 넘겨줄 수 있는데,
이렇게 되면, 하위 컴포넌트에서 bindActionCreator를 import할 필요 없다.
bindActionCreator를 사용하지 않는다면 하위컴포넌트는 해당 store가 어떤 액션생성함수를 사용하는지 알아야한다.
(bindActionCreator로 사용하는 액션생성함수를 포장해서 넘겨주면 좀 더 사용이 용이할 것 같다.)

 

https://redux.js.org/api/bindactioncreators

예시 

import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import * as TodoActionCreators from './TodoActionCreators'
console.log(TodoActionCreators)
// {
//   addTodo: Function,   //액션생성자 함수
//   removeTodo: Function
// }

function TodoListContainer(props) {
  // Injected by react-redux:
  const { dispatch, todos } = props

  // Here's a good use case for bindActionCreators:
  // You want a child component to be completely unaware of Redux.
  // We create bound versions of these functions now so we can
  // pass them down to our child later.

  const boundActionCreators = useMemo(
    () => bindActionCreators(TodoActionCreators, dispatch),
    [dispatch]
  )
  console.log(boundActionCreators)
  // {
  //   addTodo: Function,
  //   removeTodo: Function
  // }

  useEffect(() => {
    // Note: this won't work:
    // TodoActionCreators.addTodo('Use Redux')

    // You're just calling a function that creates an action.
    // You must dispatch the action, too!

    // This will work:
    let action = TodoActionCreators.addTodo('Use Redux')
    dispatch(action)
  }, [])

  return <TodoList todos={todos} {...this.boundActionCreators} />

  // An alternative to bindActionCreators is to pass
  // just the dispatch function down, but then your child component
  // needs to import action creators and know about them.

  // return <TodoList todos={todos} dispatch={dispatch} />
}

export default connect(state => ({ todos: state.todos }))(TodoListContainer)