Redux

Steve·2023년 8월 20일
1
  • 앱 상태 저장,관리
  • react-redux를 통해 리액트 사용

복잡한 트리구조에서 state관리를 유연하게 하기 위해
따로 "정보 저장 곳간" 을 만들어두고 사용한다.

아래 4개가 key point

Component : 화면에 보여줄 뷰, 웹이 될것
store : 정보 저장 공간
action
reducer

컴포넌트에서 정보 변경이 일어나면 다이렉트로 store에 값 변경을 하는게 아니라
action과 reducer를 사용해서 관리함

컴퍼넌트 -> action (dispatch 통해)

  • 컴포넌트에서 액션으로 디스패치 통해 액션으로 호출

action -> reducer (handle 통해)

  • 액션에 정의되어 있는 내용이 reducer에 의해 핸들링 됨

reducer -> store

  • 핸들링에 따라 상태값이 update 됨

store -> component

  • 업데이트된 store를 subscribe를 통해 실시간으로 component에 받아와서 사용할 수 있게됨

먼저 바닐라로 리덕스 사용
npm init -y 기본옵션으로 package.json 설치
리덕스 설치 : npm i redux

리덕스 불러오기 위해 store.js 파일 생성
es6의 import가 아닌 nodejs의 require를 통해 가져와보자

확인위해 터미널에 node store.js 입력하면 여러 펑션들이 들어있는 오브젝트 반환된걸 확인할 수 있음

  • 목표 -
    관건 : action 과 reducer를 통해 store 제어!

마지막 하단에 subscribe를 통해 view에서 어떤식으로 사용하고 dispatch도 어떻게 사용하는지 알아보자

코드 작성 법시작

  1. 액션
    object를 리턴함
    그 안에 type 컨벤션

  2. 리듀서
    리듀서에서 액션을 핸들링함
    인자로 2개를 받아야함

  • state ( 초기값으로 initialState를 보통 만들어줌)
  • action
    state값에 값이 들어오지 않으면 초기값을 설정해주겠다.
    (state= initialState, reducer)
    reducer 안에서 switch를 통해 액션의 타입을 인자로 핸들링한다
    spread operator: 객체, 배열을 다 벗겨준다.
    새로운 메모리를 만들어준다
  1. 스토어
    store를 생성해서 reducer와 action을 적용시켜보자
    const createStore = redux.createStore;
    const store = createStore(reducer); // 인자로 reducer를 넣어준다
    console.log(store)
    -->> dispatch, subscribe, getState, replaceReducer

store.getState -->> reducer함수의 첫번째 인자인 initialState값

dispatch를 통해 store 제어
store.dispatch(addSubscriber()) // addSubscriber는 type 프로퍼티 있는 함수
이를 해주면 reducer가 작동을 하면서 상태에 값이 변동이 발생함
store.getState()를 다시 한 번 확인해 보면 1이 상승해있음

  1. subscribe
    store.subscribe(()=>{}) // 함수를 인자로 받음

미들웨어

설치 : npm i redux-logger

미들웨어를 적용시키기 위해서 리덕스에 있는 기능 한개를 더 불러와야 하는데 그건 바로 applyMiddleware

미들웨어란? 왜 하는지?

그리고 node store.js 확인하고자 하는 파일 명령어 치면
상세한 로그를 확인할 수 있음

  • 발생 시간
  • 이전값과 현재값
  • 어떤 액션이 발생했는지

*여기서 잠깐!
require랑 import랑 es5, es6의 차인데 왜 es5를 쓰는 거지?
require는 NodeJS에서 사용하고 있는 CommonJS 키워드이고 import는 ES2015에서 새롭게 도입된 키워드
요새는 ES6가 많이 쓰여 import가 주로 사용되고 있지만 <script> 태그를 사용하는 브라우저 환경이나, CommonJS를 기본으로 채택하고 있는 NodeJS 등이 있기 때문에 100% import로 대체되기는 어렵다.

두 키워드 모두 동일한 목적을 가지고 있지만다른 문법구조를 가지고 있다는 것을 알 수 있었으며, Babel과 같은 ES6 코드를 변환해주는 도구 없이는 import키워드를 사용할 수 없어 require 키워드를 사용해야합니다.
예시로 번들링을 위해 사용하는 webpack은 node.js 환경에서 구동하므로 require을 사용해야 합니다. 하지만 리액트 라이브러리를 사용할 때는 import구문이 사용이 가능합니다.
이유는?
babel에서 imoprt 구문을 -> require 구문으로 변환해줌

따라서 Babel과 같은 ES6 코드를 변환해주는 도구를 사용하지 않는다면 require 키워드를 사용해야한다.

리듀서가 여러개인 경우 처리하는 방법을 보여주기 위하여 이름좀 바꿨다.

2개의 리듀서를 함께 store에 넘기기위해 필요한게 combineReducer // redux 라이브러리에 포함되어있음

실습

리덕스의 기능을 확장시키기 위해서 미들웨어 적용하는 법까지

폴더 구조는 개발자 마음이다
action, reducer, store를 구조화시켜 사용하는건 변함이 없다

기본적으로 store로 바깥에 작성하고
각각 폴더에 리듀서와 액션을 몰아넣고 스토어에서 가져와서 사용.

리덕스 폴더를 따로 만들어 관리
그안에 store.js 파일에 actions, reducer, store 정의 시작
리덕스 안에 또 다른 폴더로 subscribers에 관련된 기능에 대한 리덕스 코드를 작성하는 폴더 하나 생성. 그안에 actions.js, reducer.js 생성

리덕스를 사용하기 위해 기본앱을 Provider로 감싸야함

그리고 prop으로 store를 넣어줌

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <Subscribers />
      </div>
    </Provider>
  );
}

리덕스 세팅 끝


화면에 노출되고 있는 Subscribers라는 컴포넌트에 리덕스를 가지고 와서 사용해주면 됨

connect라는게 필요함 >> 리덕스와 연결위해

state를 펑션 형태로 작성해서 connect의 인자로 넣어야함

구독하기 버튼을 눌렀을때 숫자 상승을 위해 dispatch를 통해 보내야함
dispatch 위해 actions 필요(addSubscriber 같은)

const mapDispatchToProps = (dispatch) => {
  return {
    addSubscriber: () => dispatch(addSubscriber()), // 실행
  };
}; // function or Object
export default connect(mapStateToProps, mapDispatchToProps)(Subscribers);

조회수 다루는 리덕스도 만들어보기

위와 똑같은 형식으로 만들고

2개의 리듀서를 combine을 통해 합치기

따로 리듀서 합쳐주는 파일도 만들어서 분리

import { combineReducers } from "redux";
import subscriberReducer
import viewReducer

const rootReducer = combineReducer({
  view: viewReducer,
  subscribers: subscribersReducer,
)}

export 해주고 store에서 사용

const store = createStore(rootReducer);

다 합칙로 났더니 잘 나오던 숫자가 안나오기 시작한다

이유는?
state의 구조가 바뀜.
원래 하나만 있던게 2개로 변경되면서 프로퍼티 벨류 구조가 살짝 변경
컨솔 찍어보면 나옴

디스트럭처링도 해주면 더 -깔금-

Views에 대한 파일도 하나 더 만들어주려는데 Subscribers 파일이랑 내용이 완전 똑같아서 파일 복붙 한다음 이름들과 파일경로 조금 바꿀거임

나머지는 내용이 다같은데 액션만 조금 다르다
액션을 가져오는 폴더를 그때그때 바꿔주는게 힘들다 고로, 한곳에서 관리하면 좋음

index.js 하나 만들고 그안에서 모든 액션들을 다 때려 넣고 export

// rootReducer 안에 있는 프로퍼티 값이랑 매칭을 안시켜서 계속 에러가 났음
// Views.js에서 mapStateToProps에선 views, rootReducer안에서는 views라고 함

<input
  type="text"
  value={number}// 숫자를 바꿀때마다 숫자가 기입됨
  onChange={(e) => setNumber(e.target.value)} // e.target.value가 내가 입력한 값
/>

생성한 액션 addView()에 인자로 새로 생성한 number를 넣어줘

// action 안에는 넘긴 type, payload가 있음
const viewsReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_VIEW:
      return {
        ...state,
        count: state.count + action.payload, // 1이 아닌 넘겨받은 숫자를 기입
      };
    default:
      return state;
  }
};

여기서 잠깐, 폴더 구조를 한 번 파악하고 넘어가야겠음

미들웨어 사용해보자!

바닐라에선 리덕스 로거를 사용했었음
리덕스 로거를 미들웨어로 걸어줘보자

미들웨어를 넘기기 위해서 applyMiddleWare from 'redux'필요

import logger from "redux-logger";
const store = createStore(rootReducer, applyMiddleware(logger)); // store 생성 >> 리듀서를 인자로 넣음

state변경시킬때마다 로그 찍힘
언제, 어떤 상태값에서 어떤걸로 바꼈는지, 어떤 액션이 실행됐는지

미들웨어가 여러개가 될 수 있어서 이와같이 한다

const middleware = [logger];
const store = createStore(rootReducer, applyMiddleware(...middleware)); // store 생성 >> 리듀서를 인자로 넣음
profile
Front-Dev

0개의 댓글