Redux

chaaerim·2022년 7월 4일
0

Redux

목록 보기
1/2

Redux

프로젝트에서 rtk를 사용하기 위해 리덕스를 처음부터 공부해보면서 작성한 글입니다.....

리덕스의 필요성

Redux: 상태관리 툴
React는 상태관리가 없기 때문에 상태관리를 위한 선택지가 생김. → 이것이 Redux.
리덕스의 특징: 단방향이다.
페이스북이 양방향의 단점을 해결하기 위해 flux구조가 나옴.
state를 하나에 몰아넣고 Redux로 관리하는 것 - 컴포넌트 간의 state 사용을 안할 수 있게 됨.
컴포넌트 간의 state를 넘겨줄 때 힘들기 때문에 Redux로 관리했음.
그러면 그냥 하나의 컴포넌트 안에서만 사용하는 state은 얼마든지 사용 가능.


리덕스 구조 그림으로 정리


action과 리덕스의 장단점

store에 들어있는 데이터를 state라 부름.
단방향을 유지하기 위해 action을 만듦.
action은 state을 어떻게 바꿀지에 대한 행동을 적어 놓은 것.
실행은 dispatch라 부름.
action을 미리 만들어놓고 dispatch를 이용해서 state를 바꿈.
action은 왠만하면 미리 만들어 놓아야하고 state의 불변성을 지켜주어야 타임머신 기능을 사용할 수 있음.
state 객체를 매번 새로 만들어야 함. → Reducer action이 실행되면 Reducer에서 새로운 객체를 만들어내서 state를 대체함.
즉 Reducer는 새로운 state를 만들어 주는 애..!
dispatch와 Reducer 사이에 있는 것이 미들웨어
dispatch(action) → 함수 따라서 기록이 다 남음.
history가 다 남기 때문에 디버깅이 매우 쉬움. 밑에서 위로 거슬러 올라가면서 에러를 찾을 수 있음.


action

//action
const changeCompA={
    type: 'CHANGE_COMP_A', 
		data: 'b',

}
  • 객체 acton은 추상적으로 만드는 것이 좋음.
  • action은 함수가 아니라 객체
  • 위의 action은 compA를 b로 바꾸는 것.
  • 확장성 있게 만들어주어야 변수를 만드는 일을 줄일 수 있음.
const changeCompA=(data)=>{
    return{
        type: 'CHANGE_COMP_A'
        data,
    }
}
  • 이렇게!!
  • 다만 함수 자체가 action이 아니라 return 내부에 있는 객체 자체가 action.
  • 함수 자체는 action을 만들어 내는 creator

Redux를 사용하면 화면은 알아서 바뀜.
store.subscribe()라는 이벤트 리스너가 있긴 하지만 얘는 react-redux 내부에 들어있음.
subscribe() 내부에 화면 바꿔주는 코드가 들어간다고 생각하면 됨.
내가 기존의 state를 어떻게 바꿀 것인가를 생각해서 action을 만들면 됨.
action이 있으면 그에 맞는 reducer도 있어야함.
action과 reducer를 짝이라고 생각하라.
store의 state는 불변성을 지킨다고 했음 → 즉 대체된다.

따라서

const initialState={
	user: null,
	posts: [],
}

const nextState={
	...initialState,
	posts: [action.data],
}

const nextState={
	...initialState,
	posts: [...initialStats.posts,action.data]
}
  • 리덕스 스토어 state에서 이런 방식으로 배열에 추가됨을 알아둬라..


리덕스 예제 코드

example.js

const { createStore } = require('redux');

const reducer = (prevState, action) => {
  //액션은 객체이므로 action.type으로 씀!!!!!!
  switch (action.type) {
    case 'LOG_IN':
      return {
        ...prevState,
        user: action.data,
      };
    case 'LOG_OUT':
      return {
        ...prevState,
        user: null,
      };
    case 'ADD_POST':
      return {
        ...prevState,
        posts: [...prevState.posts, action.data],
      };
    default:
      return prevState;
  }
};
const initialState = {
  user: null,
  posts: [],
};

//store만들고 state초기화
const store = createStore(reducer, initialState);

console.log(store.getState());

//action은 객체 acton은 추상적으로 만드는 것이 좋음.
//action들은 미리 만들어 놓는 것
const login = (data) => {
  return {
    type: 'LOG_IN',
    data,
  };
};

const logout = () => {
  return {
    type: 'LOG_OUT',
  };
};

const addPost = (data) => {
  return {
    type: 'ADD_POST',
    data,
  };
};

//이 밑에부분은 리액트에서 사용하는 코드.
store.dispatch(
  login({
    id: 1,
    name: 'chaaerim',
    admin: true,
  })
);

console.log(store.getState());

store.dispatch(
  addPost({
    userId: 1,
    id: 1,
    content: 'hi',
  })
);

console.log(store.getState());

store.dispatch(logout());

console.log(store.getState());

thunk가 비동기 제어에 부족한 면이 있다면 saga를 사용하면 됨.
간단한 프로젝트에서는 thunk가 더 편하다고 함.


Reducer 분리

리듀서는 함수. 따라서 리듀서는 나눠 쓸 수가 없음
리듀서를 나눠쓰기 위해서 리덕스에서는 combineReducer를 제공.


reducers/index.js

const { combineReducers } = require('redux');

const userReducer = (prevState, action) => {
  //액션은 객체이므로 action.type으로 씀!!!!!!
  switch (action.type) {
    case 'LOG_IN':
      return {
        ...prevState,
        user: action.data,
      };
    case 'LOG_OUT':
      return {
        ...prevState,
        user: null,
      };
    default:
      return prevState;
  }
};

const postReducer = (prevState, action) => {
  switch (action.type) {
    case 'ADD_POST':
      return {
        ...prevState,
        posts: [...prevState.posts, action.data],
      };
    default:
      return prevState;
  }
};

// 모듈로 만들어주면 됨
module.exports = combineReducers({
  user: userReducer,
  posts: postReducer(),
});
  • 리듀서를 나눠서 썼기 때문에 얘도 다른 파일에 나눠서 쓸 수가 있음.

reducers/post.js

const postReducer = (prevState, action) => {
  switch (action.type) {
    case 'ADD_POST':
      return {
        ...prevState,
        posts: [...prevState.posts, action.data],
      };
    default:
      return prevState;
  }
};

module.exports = postReducer;

reducers/user.js

const userReducer = (prevState, action) => {
  //액션은 객체이므로 action.type으로 씀!!!!!!
  switch (action.type) {
    case 'LOG_IN':
      return {
        ...prevState,
        user: action.data,
      };
    case 'LOG_OUT':
      return {
        ...prevState,
        user: null,
      };
    default:
      return prevState;
  }
};

module.exports = userReducer;

reducers/index.js
const { combineReducers } = require('redux');
const userReducer=require('./user')
const postReducer=require('./post')

// 모듈로 만들어주면 됨
module.exports = combineReducers({
  user: userReducer,
  posts: postReducer(),
});
  • export import 로 사용할 수도 있지만 common js가 기본. requrie로 할 수도 있음.
  • combineReducers의 user, posts 부분이 initialState의 user, post 부분을 따라간다고 보면 됨.
  • 따라서 데이터 구조를 잘 잡아야 전체적인 구조를 잘 잡을 수 있음.

reducers/post.js

//리듀서를 쪼개면서 initialState도 같이 기본값을 넣어주어야함.

const initialState = [];

const postReducer = (prevState = initialState, action) => {
  switch (action.type) {
    case 'ADD_POST':
      return [...prevState, action.data];
    default:
      return prevState;
  }
};

module.exports = postReducer;
  • 리듀서를 쪼개면서 더이상 post는 하나의 배열이지 객체가 아니므로 객체 형태(post : —)로 쓸 필요 없음.
  • 리듀서를 달면 그에 상응하게 action도 달아주어야 함.

리덕스 미들웨어

action은 객체 dispatch는 그냥 action을 디스패치. 이 사이에 비동기가 들어갈 틈이 없음.
디스패치와 리듀서 사이에서 동작하는 것이 리덕스 미들웨어. → redux-thunk, redux-saga를 많이 사용.
미들웨어가 없으면 기본적으로 dispatch(action)을 해줌.
dispatch(action)이 reducer로 전달되는 것이 기본 동작.


미들웨어 ex code
const firstMiddleWare = (store) => (next) => (action) => {
  console.log(action);
  dispatch(action);
}; 
  • 기본동작인 diapatch(action)을 하기 전에 console.log()를 하는 동작을 넣어 준것!
  • dispatch(action) 전 후로 미들웨어에 기능을 추가할 수 있음.
  • 미들웨어는 기본적으로 삼단 함수로 구성.

redux-thunk

redux-thunk는 비동기 처리를 할 때 액션을 함수로 넣어줌.
비동기 action creator를 미들웨어를 통해서 추가를 한 다음에 동기 한 번 부르고 비동기 작업 끝낼 때 동기 한번 부르고, 동기 액션들간의 실행 순서를 조작하는 정도.
로그인, 회원가입과 같이 서버를 한번 거쳐야하는 비동기 작업을 하기 위해 redux와 약속→ 앞으로 비동기일 때 action은 함수를 넣어 dispatch를 해주면 함수를 thunk보고 실행해달라고 하는 것.


const thunkMiddleWare = (stroe) => (dispatch) => (action) => {
  if (typeof action === 'function') {
    //비동기인 경우에는 액션을 함수로 넣어주겠다는 것.
    return action(store.dispatch, store.getState);
  }
  return dispatch(action);
};

actions/user.js

const logIn = () => {
  //async action creator
  return (dispatch, getState) => {
    // 비동기 액션
    dispatch(logInRequest(data));
    try {
      setTimeout(() => {
        dispatch(
          loginSuccess({
            userID: 1,
            nickname: 'zerocho',
          })
        );
      }, 2000);
    } catch (e) {
      dispatch(logInFailure(e));
    }
  };
};

const logInRequest = (data) => {
  return {
    type: 'LOG_IN_REQUEST',
    data: {
      id: 'zerocho',
      password: 'babo',
    },
  };
};

const loginSuccess = (data) => {
  return {
    type: 'LOG_IN_SUCCESS',
    data,
  };
};
  • thunk가 비동기 액션을 처리해주므로 이제 비동기 액션을 넣을 수 있음.
  • 먼저 logInRequest를 보내고 서버에서 요청이 성공하면 loginSuccess 그렇지 않으면 logInFailure를 보냄. 항상 이러한 세 단계로 구성→ 요청, 성공, 실패
  • 비동기 액션은 원래는 리덕스에 없는 개념인데 미들웨어를 추가를 해서 비동기 액션을 만들고 동기 실행하고 비동기 작업을 실행하고 이것이 완료되면 다시 동기작업을 실행하는 것.
  • 비동기 액션은 그냥 사용자와 redux-thunk와 약속한 것.
  • 비동기 작업이 얽혀서 작업이 어려워지면 saga를 사용. 간단한 작업을 할 때에는 리덕스 thunk를 사용.
  • 액션 타입은 변수, 상수로 지정해주자.

immer

immer의 기본 형태

nextState=produce(prevState, (draft)=>{})
  • produce는 이전 state를 바탕으로 action을 받아서 다음 state를 만듦.
  • prevState는 불변성이 깨지므로 바뀌면 안됨.
  • 대신 복사본인 draft를 바꿀 수 있음. (draft는 prevState의 복사본!!!!!!)

return produce(prevState, (draft) => {
    switch (action.type) {
      case 'LOG_IN_REQUEST':
        draft.data = null;
        draft.isLoggingIn = true;

      case 'LOG_IN_SUCCCESS':
        draft.data = null;
        draft.isLoggingIn = false;
      default:
        return prevState;
    }
  });
};
  • 그러면 위와 같이 작성 가능.
  • 그렇다면 알아서 prevState와 draft가 무엇이 달라졌는지 immer가 계산하여 다음 state로 만들어줌.
  • return produce(prevState, (draft) => { 내부에 똑같이 return 코드를 넣으면 그대로 실행됨.
  • 그러면 스프레드 연산자 쓰지 않고 복사본인 draft.data에 직접 접근해서 바꿀 수 있음.

0개의 댓글