sparta TIL

24.05.28 Redux 1,2,3

hr7 2024. 5. 25. 22:36

Redux 1 - 소개 및 설정 + counter 앱 세팅

[ 전역상태 라이브러리인 Redux! ]

✔️ 리덕스가 필요한 이유

∙ useState를 쓸 때 (조부모-부모-손자 식의) 컴포넌트 단계로 인한 prop drilling의 불편함

→ 리덕스는 State를 공유하고자 할때 부-모 관계가 아니여도 되고, 중간에 의미없이 컴포넌트를 거치지 않아도 된다. 

 자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있다. 

 

✔️ Global state(Redux)와 Local state(useState)

 Local state (지역상태)

   컴포넌트에서 useState를 이용해서 생성한 state. 좁은 범위 안에서 생성된 State 이다.

 Global state (전역상태)

   Global state는 컴포넌트에서 생성되지 않으며, 중앙화 된 특별한 곳에서 State들이 생성된다.

   쉽게 말해 “중앙 state 관리소” 라고 생각하면 된다.

 

<참고>
Context API가 있음에도 Redux로 Global state를 관리하면 좋은 이유?
Context API는 React 자체적으로 제공하는 전역 상태 관리 도구이기 때문에 Global State를 관리하는 한 가지의 옵션이 될 수 있지만, 복잡하지 않은 상태 관리 요구사항에 적합하다.
대규모 어플리케이션에서는
Context API만으로는 다음과 같은 제한이 있어 Redux를 사용하는 것이 더 효과적일 수 있다.

  1. 성능 최적화
    Context API는 Provider 하위의 모든 컴포넌트를 리렌더링하게 할 수 있다. 하지만 상태가 변경될 때마다 관련된 모든 컴포넌트가 불필요하게 업데이트 되는 것을 막기 위해 최적화가 필요하다.
    반면, Redux는 상태 변경 시 관련된 컴포넌트만 선택적으로 업데이트할 수 있어 성능 관리가 용이하다.
  2. 상태 로직의 중앙화와 일관성
    Redux는 애플리케이션의 상태를 하나의 저장소(store)에 저장한다. 이로 인해 상태 로직이 중앙에서 관리되어 더 일관성 있고 예측 가능한 상태 변경이 가능해진다. 또한, 모든 상태 변경 로직이 리듀서(reducers)에 의해 처리되기 때문에 디버깅과 테스팅이 용이하다.
  3. 강력한 미들웨어와 개발 도구
    Redux는 다양한 미들웨어를 지원하여 비동기 작업, 로깅, 상태 변경에 대한 추가 처리 등 복잡한 기능을 구현할 수 있다. 또한 Redux DevTools와 같은 강력한 개발 도구를 통해 상태 변화를 시각적으로 모니터링하고 이전 상태로 롤백하는 등의 기능을 제공한다.

 

[ Redux란? ]

'중앙 state 관리소'를 사용할 수 있게 도와주는 패키지(라이브러리)이다. → 리덕스 : "전역 상태관리 라이브러리"

“중앙 state 관리소" 를 통해서 State를 관리한다는 아이디어는 굉장히 좋으나

우리가 그것을 직접 구현하기는 어렵기 때문에 패키지(라이브러리)의 도움을 받아 그것을 구현해보자

 

 

 

 

 

 

 

 

 

 

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

const App = () => {
  const counterReducer = useSelector((state) => {
    return state.counter;
  });
  console.log("state", counterReducer);
  return <div>App </div>;
};

export default App;
// src/redux/config/configStore.js
import { combineReducers, createStore } from "redux";
import counter from "../modules/counter";

// 1. rootReducer를 만들거에요
const rootReducer = combineReducers({
  counter,
});

// 2. store를 생성
const store = createStore(rootReducer);

// 3. 만든 store를 내보낼거에요
export default store;
// src/redux/modules/counter.js
// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서 : 변화를 일으키는 함수
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default counter;
// main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./redux/config/configStore.js";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

 

..무작정 따라하기 .. . .. ;

 

 


Redux 2 - counter 앱 완성, redux 사이클 확인, dispatch

redux의 사이클

1. View 에서 액션이 일어난다.

2. dispatch 에서 action이 일어나게 된다.

3. action에 의한 reducer 함수가 실행되기 전에 middleware가 작동한다.

4. middleware 에서 명령내린 일을 수행하고 난뒤, reducer 함수를 실행한다. (3, 4번은 아직 몰라도 됩니다!)

5. reducer 의 실행결과 store에 새로운 값을 저장한다.

6. store의 state에 subscribe 하고 있던 UI에 변경된 값을 준다.

 

( counterApp 이어서 만들기 )

1. 뷰→리듀서로 액션보내는 것 까지 ( ⭐️dispatch : 뷰의 action을 리듀서에 보내는 역할 )
2. 1의action을 받고, state에 반영헤서 다시 뷰로 보내줌 ! ( 클릭시 1씩 증가 )

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

const App = () => {
  const counterReducer = useSelector((state) => state.counter);

  const dispatch = useDispatch(); // 뷰의 액션을 리듀스에 보내는역할

  return (
    <div>
      {counterReducer.number}
      <button
        onClick={() => {
          dispatch({
            type: "PLUS_ONE",
          });
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          dispatch({
            type: "MINUS_ONE",
          });
        }}
      >
        -1
      </button>
    </div>
  );
};

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;

클릭에 따라 +,- 숫자가 잘 출력된다 🤩

⭐️ 주요 개념 정리

  • action객체란, 반드시 type이란 key를 가져야 하는 객체이다. 또한 리듀서로 보낼 '명령'이다.
  • Dispatch란, action객체를 리듀서로 보내는 “전달자” 함수이다.
  • 리듀서란, Dispatch를 통해 전달받은 action객체를 검사하고, 조건이 일치했을 때 새로운 상태값을 만들어내는 '변화를 만들어내는' 함수이다.
  • 디스패치(dispatch)를 사용하기위해서는 useDispatch() 라는 훅을 이용해야 한다.
    • Dispatch는 스토어의 내장함수 중 하나이다.
    • 우선, Dispatch는 action을 발생 시키는 것 정도로 이해하기
    • dispatch 라는 함수에는 action을 파라미터로 전달한다. ( dispatch(action) 이런식으로 )
  • 액션객체 type의 value는 대문자로 작성한다. (JS에서 상수는 대문자로 작성하는 룰이 있음)

[ Redux 3 - action creators, action values, payload, Ducks 패턴 ]

 

[ Action Creator ]

1. 왜 사용하나?

  ① 휴먼에러 (오타) 방지

 

 

 

 

 

 

 

 

  ② 유지보수의 효율성 증가 : 만약Action Creator가 100군데에서 쓰이고 있는 상태에서 바꾸어야 하는 상황이 오면

                                           단 한번의 수정으로 100군데에 모든 수정사항을 반영할 수 있다.

  ③ 코드 가독성 : 모듈 파일에서 Action Creator가 잘 정리 되어있으면, 다른 개발자가 봤을때 해당 모듈이 가지고 있는

                          모든Action들을 한눈에 알 수 있다. 즉,그 자체가Action들의 리스트업을 해주는 역할을 갖게된다.

 

이전 코드처럼 하드코딩을 하는 것이 아니라, 액션객체를 한 곳에서 관리할 수 있도록 '함수'와 액션value를 상수로 만들어보자.

▼ 1_'PLUS_ONE'액션 객체를 만드는 함수의 예. 이것이 Action Creator이다. 해석 그대로 액션을 만드는 생성자 !

// src/redux/modules/counter.js
const PLUS_ONE = "PLUS_ONE"; // value는 상수로 생성
// 액션객체를 반환하는 함수 생성
// export 가 붙는 이유는 plusOne()는 밖으로 나가서 사용될 예정이기 때문
export const plusOne = () => { 
  return {
    type: PLUS_ONE, // type에는 위에서 만든 상수로 사용 
  };
};

 

▼ 2_위의 변경 된 액션의value상수를 이용해 액션객체를 반환하는 함수를 작성, 이것을 실제로 리듀서와 컴포넌트에 반영

// src/modules/counter.js
// 추가된 코드 👇 - 액션 value를 상수들로 만들어 준다. 보통 이렇게 한곳에 모여있음
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "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: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣는다.
      return {
        number: state.number + 1,
      };
    case MINUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣는다.
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

▼ 3_Action Creator 사용하기

  ☝🏻export된 Action Creator import 하기 , ✌🏻dispatch()에 있던 액션객체를 지우고, Action creator 넣기 

// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";

// 사용할 Action creator를 import 합니다.
import { minusOne, plusOne } from "./redux/modules/counter";

const App = () => {
  const dispatch = useDispatch();
  const number = useSelector((state) => state.counter.number);

  return (
    <div>
      {number}
      <button
        onClick={() => {
          dispatch(plusOne()); // 액션객체를 Action creator로 변경
        }}
      >
        + 1
      </button>
      {/* 빼기 버튼 추가 */}
      <button
        onClick={() => {
          dispatch(minusOne()); // 액션객체를 Action creator로 변경
        }}
      >
        - 1
      </button>
    </div>
  );
};

export default App;

[ payload ]

1. Payload?

   정해진 기능을 만드는 것이 아니라, 증가 시킬 숫자를 카운터 프로그램을 사용하는 사용자가 직접 정할 수 있게 하는 기능

// payload가 추가된 액션객체
{type: "ADD_NUMBER", payload: 10} // type뿐만 아니라 payload라는 key와 value를 같이 담는다.

 


[ Ducks 패턴 ]

Ducks 패턴이란?

Redux 앱을 구성할 때 사용하는 방법론 중 하나로 일반적으로 분산되어 있던 액션 타입, 액션 생성자, 리듀서를 하나의 파일로 구성하는 방식이며 Redux 관련 코드의 관리를 보다 간결하게 모듈화하여 관리하도록 한다.

  1. Reducer 함수를 export default 한다.

  2. Action creator 함수들을 export 한다.

  3. Action type은 app/reducer/ACTION_TYPE 형태로 작성한다.


action creators, action values, payload, Ducks 패턴 모두 적용하여 수정 한 최종코드

// App.js
import { useSelector, useDispatch } from "react-redux";
import { addNumber, removeNumber } from "./redux/modules/counter.js";
import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const counterReducer = useSelector((state) => state.counter);
  const dispatch = useDispatch(); // 뷰의 액션을 리듀스에 보내는역할

  return (
    <div>
      {counterReducer.number}
      <br />
      <input
        type="number"
        value={count}
        onChange={(e) => {
          setCount(+e.target.value);
        }}
      />
      <button
        onClick={() => {
          dispatch(addNumber(count));
        }}
      >
        더하기
      </button>
      <button
        onClick={() => {
          dispatch(removeNumber(count));
        }}
      >
        빼기
      </button>
    </div>
  );
};

export default App;
// counter.js
// 초기 상태값
const initialState = {
  number: 0,
};

// const PLUS_ONE = "PLUS_ONE";
// const MINUS_ONE = "MINUS_ONE";
const ADD_NUMBER = "ADD_NUMBER";
const REMOVE_NUMBER = "REMOVE_NUMBER";

// 액션 객체를 만드는 action creat함수
export const addNumber = (payload) => {
  return {
    type: ADD_NUMBER,
    payload,
  };
};
export const removeNumber = (payload) => {
  return {
    type: REMOVE_NUMBER,
    payload,
  };
};

// 리듀서 : 변화를 일으키는 함수
const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        number: state.number + action.payload,
      };
    case REMOVE_NUMBER:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

export default counter;

 

 


알고리즘 과제

- split : x를제외한 배열생성

- sort : a-b-c 사전순 정렬

- filter : ' ' 빈문자열이 아닌 것들로 걸러줌