230623 Redux

나윤빈·2023년 6월 24일
0

TIL

목록 보기
8/55

1) Redux가 필요한 이유

useState를 통해 컴포넌트에서 생성한 state를 다른 컴포넌트로 보내고자 할 때 Props를 통해서 부모 컴포넌트에서 자식 컴포넌트로 그 값을 보내준다. 하지만 props로 state를 공유하는 방법에는 불편함 점 존재한다.

➡️ 컴포넌트에서 컴포넌트로 state를 보내기 위해서는 반드시 부-모 관계가 되어야 한다.
➡️ 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자 할 때에도 반드시 부모 컴포넌트를 거쳐야만 한다. (정작 부모 컴포넌트에서는 그 값이 필요가 없어도 단순히 손자 컴포넌트에게 전달하기 위해 불필요하게 거쳐야만 하는 것이다.)
➡️ 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.

💡 리덕스를 사용하면 state를 공유하고자 할 때 부-모 관계가 아니여도 되고, 중간에 의미 없이 컴포넌트를 거치지 않아도 된다. 또한 자식 컴포넌트에서 만든 state를 부모 컴포넌트에서도 사용할 수 있다!

2) Global state와 Local state

  • Local state (지역상태) : 컴포넌트에서 useState를 이용해서 생성한 state로 좁은 범위 안에서 생성된 state라고 할 수 있다.
  • Global state (전역상태) : 컴포넌트가 아니라 중앙화 된 특별한 곳에서 state를 생성한다. 즉, '중앙 state 관리소'라고 할 수 있다.

useState로 생성한 state는 Local State이고, Redux에서 생성한 State는 Global State이다.

3) Redux란?

✅ 리덕스는 전역 상태 관리 라이브러리이다.

✅ 리덕스는 중앙 state 관리소를 가지고 있으며, 모든 state는 이곳에서 생성된다.

4) 폴더구조

redux : 리덕스 관련 코드를 모두 몰아넣음
config : 리덕스 설정 관련 파일 전부
configStore : 중앙 state 관리소와 관련된 설정 코드
modules : 중앙 state에서 관리하는 state의 그룹

4-1) 중앙 데이터 관리소(store)를 설정

configStore.js

import { createStore } from "redux";
import { combineReducers } from "redux";

const rootReducer = combineReducers({});
const store = createStore(rootReducer);
  • rootReducer란? Reducer를 하나로 모아서 한 개로 만들어 놓은 기본 Reducer
  • modules 안에 넣어 놓은 state의 묶음들을 객체 형태에다가 몰아넣음
  • 웹 애플리케이션 내에서 관리하는 state 집단들이 rootReducer로 들어옴
  • 모든 컴포넌트들은 props로 값을 내려주지 않더라도 중앙 데이터 관리소로 데이터를 바라볼 수 있게 됨

4-2) store를 애플리케이션에 주입

configStore.js

export default store;

index.js

import { Provider } from "react-redux";
import store from "./redux/config/configStore";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);
  • App 컴포넌트가 Provider의 지배권 안으로 들어옴
  • Provider는 우리가 만든 store를 기반으로 지배권을 행사
  • store로 만들어 놓은 중앙 데이터 관리소를 App 컴포넌트 하부에서 store를 쓸 수 있다!

📌 export default는 import 할 때 {} 필요 없음!

5-1) Redux로 Counter 만들기

./modules/counter.js

const initialState = {
  number: 0,
};

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

export default counter;
  • 초기 상태값(state) 설정
  • Reducer는 state를 action의 type에 따라 변경하는 함수
  • action은 state를 어떻게 수정할 것인지 표현하는 것으로 type과 value를 가짐
  • 만든 Reducer(counter)를 내보냄

configStore.js

import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";

const rootReducer = combineReducers({
  counter: counter,
});
const store = createStore(rootReducer);

export default store;
  • counter라는 reducer를 import
  • combineReducers 안으로 reducer들을 넣어줌

5-2) state에서 store에 있는 counter에 접근하기 (useSelector)

App.jsx

import { useSelector } from "react-redux";
import "./App.css";

function App() {
  const data = useSelector((state) => {
    return state.counter;
  });

  console.log("data", data);

  return <div>Redux!</div>;
}

export default App;

  • useSelector를 통해서 중앙 저장소에 있는 state에 접근할 수 있음
  • 여기서 state는 중앙 저장소 전체에 있는 state 전체를 의미

5-3) store에 접근해서 counter의 값을 변경하기 (useDispatch)

  • dispatch가 action을 store에 던진다
  • action 객체? 두 가지 key를 가짐 그 중 하나가 type
  • store는 action 객체의 type에 따라 state를 변경해줌

App.jsx

iimport { useDispatch, useSelector } from "react-redux";
import "./App.css";

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });

  // dispatch 가져오기
  const dispatch = useDispatch();

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          // +1을 해주는 로직을 써준다.
          dispatch({
            type: "PLUS_ONE",
          });
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          // -1을 해주는 로직을 써준다.
          dispatch({
            type: "MINUS_ONE",
          });
        }}
      >
        -
      </button>
    </>
  );
}

export default App;

counter.js

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS_ONE":
      return {
        number: state.number + 1,
      };
    case "MINUS_ONE":
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

export default counter;

6-1) Counter 리팩토링 (Action Value)

  • reducer랑 컴포넌트의 실체 호출하는 부분이랑 action type을 맞춰줘야 함
  • 휴먼 에러가 날 수 있음
  • action이라는 type을 문자열의 형태로 dispatch와 reducer 안에다가 직접 넣는 것이 아니라 변수 형태로 관리해보자

counter.js

// action value
export const PLUS_ONE = "counter/PLUS_ONE";
export const MINUS_ONE = "counter/MINUS_ONE";

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE:
      return {
        number: state.number + 1,
      };
    case MINUS_ONE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

export default counter;
  • action value 만들기
  • case문 안에 문자열로 직접 넣지 않고 변수로 가져옴
  • action value export 해줌

App.jsx

import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { PLUS_ONE, MINUS_ONE } from "./redux/modules/counter";

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });

  // dispatch 가져오기
  const dispatch = useDispatch();

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          // +1을 해주는 로직을 써준다.
          dispatch({
            type: PLUS_ONE,
          });
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          // -1을 해주는 로직을 써준다.
          dispatch({
            type: MINUS_ONE,
          });
        }}
      >
        -
      </button>
    </>
  );
}

export default App;
  • action value import 하기

6-2) Counter 리팩토링 (Action Creator)

  • Action Creator? Action 객체를 만들어주는 역할을 하는 함수 (action value를 return 함)

counter.js

// action value
export const PLUS_ONE = "counter/PLUS_ONE";
export const MINUS_ONE = "counter/MINUS_ONE";

// action creator
export const plusOne = () => {
  return {
    type: PLUS_ONE,
  };
};
export const minusOne = () => {
  return {
    type: MINUS_ONE,
  };
};

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE:
      return {
        number: state.number + 1,
      };
    case MINUS_ONE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

export default counter;
  • action creator 함수를 만들어줌
  • action creator 함수 export

App.jsx

import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { plusOne, minusOne } from "./redux/modules/counter";

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });

  // dispatch 가져오기
  const dispatch = useDispatch();

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          // +1을 해주는 로직을 써준다.
          dispatch(plusOne());
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          // -1을 해주는 로직을 써준다.
          dispatch(minusOne());
        }}
      >
        -
      </button>
    </>
  );
}

export default App;
  • action creator 함 import
  • action creator가 반환하는 값이 action 객체

7) payload (전달되는 실체)

  • action 객체는 action type을 payload 만큼 처리하는 것

🤔 예를 들어 payload가 3이라는 것은 '3만큼을 플러스, 마이너스해라' 라는 뜻

Counter.js

// action value
const PLUS_N = "counter/PLUS_N";
const MINUS_N = "counter/MINUS_N";

// action creator
export const plusN = (payload) => {
  return {
    type: PLUS_N,
    payload: payload,
  };
};
export const minusN = (payload) => {
  return {
    type: MINUS_N,
    payload: payload,
  };
};

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_N:
      return {
        number: state.number + action.payload,
      };
    case MINUS_N:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

export default counter;
  • reducer 부분에 새로운 case문 만들기
  • action value 만들기
  • payload를 매개변수로 받는 action creator 함수 만들고 export 해주기

App.js

import "./App.css";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { plusN, minusN } from "./redux/modules/counter";

function App() {
  // useState
  const [number, setNumber] = useState(0);

  const counter = useSelector((state) => {
    return state.counter;
  });

  const dispatch = useDispatch();

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <div>
        <input
          type="number"
          value={number}
          onChange={(event) => {
            const { value } = event.target;
            setNumber(+value);
          }}
        />
      </div>
      <button
        onClick={() => {
          dispatch(plusN(number));
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          dispatch(minusN(number));
        }}
      >
        -
      </button>
    </>
  );
}

export default App;
  • action creator import 해주기
  • onClick 함수의 return으로 action creator 함수 호출해주기 (매개변수로 payload 넘겨주기, 여기서 payload는 input 값으로 받아온 number)

✅ action 객체는 type과 payload가 있다.

✅ action 객체는 payload만큼 type에 맞게 처리한다.

✅ action 객체를 store로 dispatch가 던진다.

8) Ducks 패턴

리덕스를 사용하기 위해서는 우리가 리덕스의 구성요소를 모두 만들어야만 한다. 근데 만약 리덕스 모듈을 개발하는 개발자마다 구성요소들을 다르게 구현한다면?

이를 피해기 위해 만들어진 패턴이 바로 Ducks 패턴

  1. Reducer 함수를 export default 한다.
  2. Action Creator 함수를 export 한다.
  3. Action type은 app/reducer/Action_TYPE 형태로 작성한다.

➡️ 모듈 파일 1개에 Action type, Action Creator, Reducer가 모두 존재해야 함

profile
프론트엔드 개발자를 꿈꾸는

0개의 댓글