Redux

김주형·2022년 7월 1일
0

리덕스를 사용해야하는 이유

자바스크립트 어플리케이션을 위한 라이브러리
React,Angular,vue 그리고 Vanilla에 사용
어플리케이션의 상태를 저장,관리
React-redux를 통해 리액트에 사용

리액트의 각 컴포넌트는 각각의 state를 갖고있다.
부모컴포넌트의 state를 가져오기 위하여 props를 사용하지만
부모 컴포넌트가 많아지면 일일히 props를 구현해야하기 때문에 상태 관리가 불편해진다. 이런 불편함을 해결하기 위해 redux라는 라이브러리를 사용한다.

리덕스의 core concept

component에서 store로 데이터를 직접변경하는것은 Redux의 상태관리 개념이 아니다.

component에서 dispatch를 통해 action을 호출하고
action에서 handle를 통해 reducer를 호출하면
reducer에서 반환된 state를 store에 업데이트 한다.
그리고 업데이트된 state의 정보를 subscribe를 통해 dispatch한 component에 전달한다.

바닐라 js로 리덕스 구현해보기

1.npm init -y -> package.json파일 생성
2.npm install redux
3.store.js파일 생성
4.node store.js -> 리덕스가 제대로 설치 되었는지 확인하기 위해

const redux = require('redux');
const createStore = redux.createStore; // store를 사용하기 위해
const reduxLogger = require('redux-logger'); // reduxLogger
const applyMiddleware = redux.applyMiddleware; // 리덕스 middleware (리덕스 logger)를 사용하기위해
const logger = reduxLogger.createLogger();
const combineReducers = redux.combineReducers; // 여러개의 리듀서가 있을 때 루트 리듀서를 만든다.

//actions
//action-types
const ADD_SUBSCRIBER = 'ADD_SUBSCRIBER';
const ADD_VIEWCOUNT = 'ADD_VIEWCOUNT';
const addSubscriber = () => {
  return {
    type: 'ADD_SUBSCRIBER',
  };
};
const addViewCount = () => {
  return {
    type: 'ADD_VIEWCOUNT',
  };
};

//reducers
const subscriberState = {
  subscribers: 365,
};
const subscriberReducer = (state = subscriberState, action) => {
  switch (action.type) {
    case ADD_SUBSCRIBER:
      return {
        ...state, // 원본값을 훼손하면 안되기 때문에 , immutable한 값
        subscribers: state.subscribers + 1,
      };
    default:
      return state;
  }
};

const viewState = {
  viewCount: 100,
};

// reducer는 초기 상태와 action을 인자로 받아서 새로운 상태를 리턴하는 함수이다.
// 즉 ...state 스프레드 문법으로 원본의 값은 유지한 채로 state를 가져와서
// store에 저장되어있는 state값을 새로운 state로 반환하는 함수이다.
const viewReducer = (state = viewState, action) => {
  switch (action.type) {
    case ADD_VIEWCOUNT:
      return {
        ...state,
        viewCount: state.viewCount + 1, //
      };
    default:
      return state;
  }
};

const rootReducer = combineReducers({
  view: viewReducer,
  subscriber: subscriberReducer,
});
//store
const store = createStore(rootReducer, applyMiddleware(logger)); // 2번째 인자로 middleware를 넘길 수 있다.
//subscribe - view - dispatch

// store.subscribe(() => {
//   console.log('subscribe ==>', store.getState()); // subscribe를 이용해 state가 업데이트될때마다 store의 state를 콘솔로 찍어줌
// });

// console.log(store.getState()); // { subscribers: 365 }

// store.dispatch(addSubscriber()); // dispatch를 통해 action을 호출 -> reducer가 호출되면서 새로운 state를 반환 subscribe ==> { subscribers: 366 }
// store.dispatch(addSubscriber());
// store.dispatch(addSubscriber());
// store.dispatch(addSubscriber());
// store.dispatch(addSubscriber());
store.dispatch(addViewCount());
store.dispatch(addViewCount());
// console.log(store.getState()); // { subscribers: 366 } -> 반환된 새로운 state

/*
redux-logger 출력값 debugging 하기

action ADD_VIEWCOUNT @ 15:06:55.989  
    prev state { view: { viewCount: 100 }, subscriber: { subscribers: 365 } }   // view,subscriber 는 rootReducer의 키 
    action     { type: 'ADD_VIEWCOUNT' }
    next state { view: { viewCount: 101 }, subscriber: { subscribers: 365 } }
action ADD_VIEWCOUNT @ 15:06:55.997
    prev state { view: { viewCount: 101 }, subscriber: { subscribers: 365 } }
    action     { type: 'ADD_VIEWCOUNT' }
    next state { view: { viewCount: 102 }, subscriber: { subscribers: 365 } }
*/

리덕스 코드를 작성하던 중 이해가 안되는 구문이 있었다.

const viewReducer = (state = viewState, action) => {
  switch (action.type) {
    case ADD_VIEWCOUNT:
      return {
        ...state,
        viewCount: state.viewCount + 1, //
      };
    default:
      return state;
  }
};

일단 spread-operator을 사용해서 값을 받아오는건 알겠는데 왜 key값이 2개지 라는 생각이 들었다. 그래서 혼자 값에 이것저것 넣어보며 디버깅을 해 보았다.

//prac.js

const viewState = {
  viewCount: 100,
};

// ...viewState = viewCount:100
console.log({ ...viewState, viewCount2: viewState.viewCount + 1 }); // { viewCount: 100, viewCount1: 101 }
console.log({ ...viewState, viewCount: viewState.viewCount + 1 }); // { viewCount: 101 }

// 즉 객체에서 spreadoperator연산자를 통해서 초기값을 복사한후 , 그 복사한 값에 새로 정의한 값을 넣는다.
// 앞에 ...을 한 이유는 state가 복사되는 것이고 , 그 이후 복사된 state값에 새로 정의한 state값을 넣게 되면
// 앞에 ...을 한 변수는 , 이후의 이름이 같은 변수로 오버라이드 된다. 즉 단 하나의 값만 return하게 된다.

//객체에서의 Spread Operator
let currentState = { name: '철수', species: 'human' };
currentState = { ...currentState, age: 10 };

console.log(currentState); // {name: "철수", species: "human", age: 10}

//객체의 프로퍼티를 오버라이드 한다. 이거 때문에 헷갈렸던것 같다.
//즉 객체 프로퍼티의 키 값이 같을 경우 새로 추가된 키의 value로 값이 수정된다.
currentState = { ...currentState, name: '영희', age: 11 };
console.log(currentState); // {name: "영희", species: "human", age: 11}

즉 ...state로 viewCount의 값을 가져오고 그 값을 새로운 상태로 정의해주면 기존에 ...state로 가져온 값이 새로운 viewCount로 오버라이드된다. 그렇기 때문에 return 문으로 원본을 훼손하지 않은 새로운 데이터를 출력할 수 있는것이다.

정리

리덕스는 컴포넌트의 state를 자유롭게 사용하기위해서 사용하는것이다.
중요한 점은 원본의 데이터는 수정되지 않는 immutable한 데이터여야 하기 때문에 컴포넌트가 store에 직접 접근하여 데이터를 수정하는 방법은 옳지 않은 방법이며 dispatch를 통해 action을 호출하고 action의 handle을 통해 reducer를 호출하며 그 reducer가 반환된 새로운 state를 store에 저장하게되면 store의 subscribe를 통해서 그 데이터를 컴포넌트로 가져올 수 있게 되는것이다.

추가적으로 redux-logger를 통해 상태가 어떻게 바뀌었는지 디버깅 해 볼 수 있을것같다. redux-devtools를 주로 사용하긴 하지만 간단한 데이터들은 redux-logger를 사용해봐도 괜찮을 것 같다.

profile
프론트엔드 개발 지망생입니다.

0개의 댓글