2.Redux

5o_hyun·2023년 6월 5일
0
post-thumbnail

리덕스 기초

중앙저장소(store)에는 데이터들이있다. 이 데이터를 수정 추가 등의 행동을 하고싶으면,
바꾸고싶은것을 쓴 주문서(action)를 만들어서 dispatch해주면 바꿀수있는데, 그냥 dispatch를 해주는게아니라 reducer의 양식에 맞춰 바꾼다.

즉 store의 데이터를 바꾸고싶으면, action을 만들고, dispacth를 해줄때 reducer의 양식에 맞춰 dispatch해준다.

그럼 데이터를 바꿀때마다 action과 action에 따른 reducer를 만들어줘야하냐?
그렇다.
코드량이 많아지지만 대신 좋은점은 action 하나하나 다 reducer에 기록이 되기 때문에, 지금까지 데이터를 어떻게 바꿔왔는지 추적되기때문에 테스트나 버그잡을때 편하다.

리덕스의 불변성

리덕스는 불변성이 있다. 불변성을 지켜야 모든 action이 기록되어 히스토리를 유지할수있다.
객체는 새로만들면 항상 다 다르다. {} === {} // false
리듀서에서 ...state로 앞거는 그대로 복사하고 name:action.data로 name부분만 바꿔서 저장한다.
그럼 맨 오른쪽에 보이는것처럼 객체는 새로 만들면 다 다르기때문에, 이전데이터, 이번데이터 이렇게 따로 저장된다.
다 다르게 저장하는 이유는 앞에서 말했던것처럼 전의 action을 추적해 테스트, 버그 등에 이용하기 떄문이다.

그럼 왜 통째로 저장안하고 ...state이렇게 앞에거를 복사할까?

// 이렇게저장안하고                           통째로 저장해도 되지않나? 
const prev={name: 'zerocho'}	=>?		const prev={
                                            name: 'zerocho',
                                            age:27,
                                            password:'babo'
                                        }

const next={name: 'boogicho'}	=>?		const next={
                                            name: 'boogicho',
                                            age:27,
                                            password:'babo'
                                        }

코드가 길어지는것도 있지만, 메모리를 아끼기위해서다.
...state를 하면 안바뀌는것들은 참조를 해서 유지를하지만, action할때마다 새로운 객체를 계속 생성하면 메모리를 많이 잡아먹는다.(action할때마다 age:27,password:'babo'가 계속생성됨)

미들웨어

리덕스 미들웨어는 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행한다.

미들웨어를 사용하는이유

1. 로깅 및 디버깅
미들웨어를 사용하면 로깅 및 디버깅을 용이하게 할 수 있.
Redux 미들웨어를 사용하면 액션과 상태를 가로채서 로깅하거나 디버깅하기 쉽게 할 수 있어서, 액션과 상태를 로깅하면 앱의 문제점을 찾아내기가 쉬워집니다.

2. 액션을 가로채어 추가 작업
미들웨어를 사용하면 dispatch에서 보낸 액션이 reducer로 가기 전에 가로채어 추가 작업을 할 수 있다.
(예를 들어, 리듀서를 처리하기전에 개발용에는 devtools를 연결하고 배포용에는 devtools를 연결하지 않게 해 조금이라도 더 최적화를 할수있다.)

또, 사전에 액션을 가로채지않고 리듀서 안에서 가로채서 작업을 해주는경우도있다.
(nodebird사용시 그랬음 코드참고)

[액션] => [미들웨어] => [리듀서] => [스토어]

자, 이제 써보자.

로그인,로그아웃을 반복해서 실행하다보면 state가 바뀌었으니 react-devtools에서 action이 보여야하는데 안보인다. (히스토리가 안보임)
히스토리를 보기위해 미들웨어를 붙여주자.

  1. 브라우저 개발자 도구와 연동이 되게 해주기위해 npm i redux-devtools-extension을 깐다.
  2. store을 미들웨어를 추가해 수정해준다. (configureStore에 다음과같이 해당 부분을 수정/추가해준다.)
const configureStore = (data) => {
  const middlewares = [];
  const enhancer =
    process.env.NODE_ENV === "production"
      ? compose(applyMiddleware(...middlewares)) // 배포용 => devtools 연결 x
      : composeWithDevTools(applyMiddleware(...middlewares)); // 개발용 => devtools 연결 o
  const store = createStore(reducer, enhancer);


이제 action이 잘뜬다.

리듀서쪼개기

그럼 데이터를 바꿀때마다 action과 action에 따른 reducer를 만들어줘야하냐? 그렇다.
지금과 같은 방법으로 하면 switch문의 case와 action을 계속 생성해줘야한다.
복잡하니깐 쪼개보자.

  1. 우선 내가 분리하려는 파일은 다음과같다.
import { HYDRATE } from "next-redux-wrapper";

const initialState = {
  user: {
    isLoggedIn: false,
    user: null,
    signUpData: {},
    loginData: {},
  },
  post: {
    mainPosts: [],
  },
};

export const loginAction = (data) => {
  return {
    type: "LOG_IN",
    data,
  };
};

export const logoutAction = (data) => {
  return {
    type: "LOG_OUT",
    data,
  };
};

// (이전상태, 액션) => 다음상태
const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case HYDRATE:
      return { ...state, ...action.payload };
    case "LOG_IN":
      return {
        ...state,
        user: {
          ...state.user,
          isLoggedIn: true,
          user: action.data,
        },
      };
    case "LOG_OUT":
      return {
        ...state,
        user: {
          ...state.user,
          isLoggedIn: false,
          user: null,
        },
      };
    default:
      return state;
  }
};

export default rootReducer;
  1. 맨위에 initialState에 user, post가 있으니 user.js, post.js를 따로 만들어주고 리듀서의 기본형태를 써줬다.
const initialState = {
    
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default reducer;
  1. 초기값과 케이스문에 있는걸 그대로 옮겨준다. 여기서 주의할점은 index에 import할때 한단계 댑스가 더 생기는거이므로 case문의 return시 state부분을 변경해줘야한다.

user.js

export const initialState = {
  isLoggedIn: false,
  user: null,
  signUpData: {},
  loginData: {},
};

export const loginAction = (data) => {
  return {
    type: "LOG_IN",
    data,
  };
};

export const logoutAction = (data) => {
  return {
    type: "LOG_OUT",
    data,
  };
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "LOG_IN":
      return {
        ...state,
        isLoggedIn: true,
        user: action.data,
      };
    case "LOG_OUT":
      return {
        ...state,
        isLoggedIn: false,
        user: null,
      };
    default:
      return state;
  }
};

export default reducer;

post.js

export const initialState = { mainPosts: [] };

const reducer = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default reducer;
  1. 인덱스도 고쳐준다.
import { HYDRATE } from "next-redux-wrapper";
import user from "./user";
import post from "./post";
import { combineReducers } from "redux";

// (이전상태, 액션) => 다음상태
const rootReducer = combineReducers({
  index: (state = {}, action) => {
    switch (action.type) {
      case HYDRATE:
        console.log("hydrate", action);
        return { ...state, ...action.payload };

      default:
        return state;
    }
  },
  user,
  post,
});

export default rootReducer;

리듀서를 쓰려면 useSelector로 받아서쓰고 dispatch로 액션을사용한다.

profile
학생 점심 좀 차려

0개의 댓글