20230213 [react] - 리덕스

lionloopy·2023년 2월 13일
0

리액트

목록 보기
9/18

리덕스

리덕스의 필요성
:상태관리 라이브러리이다. 중앙 데이터 관리소 정도!

  • Local stste(지역상태): 컴포넌트에서 useState를 이용해 생성한 state이다. 좁은 범위 안에서 생성된 state
  • Global state(전역상태): 컴포넌트에서 생성되지 않는다. 중앙화 된 특별한 곳에서 state들이 생성된다. 중앙 state관리소라고 생각하면 된다.

리덕스의 정의
:중앙 state관리소를 사용할 수 있게 도와주는 패키지, 즉 라이브러리이다. 전역상태 관리 라이브러리 라고도 한다.

  • 리덕스는 전역 상태 관리 라이브러리이다.
  • 리덕스는 useState를 통해 상태를 관리했을 때 발생하는 불편함을 해소시켜준다.
  • 리덕스는 중앙 state관리소를 가지고 있으며, 모든 state는 이곳에서 생성된다.
  • useState로 생성한 state는 Local state, 리덕스에서 생성한 state는 Global state이다.

Context API vs 리덕스
:context API와 리덕스는 둘 다 전역 상태 관리를 위한 도구이며, 리덕스는 context API를 가지고 만든 라이브러리이다. 따라서 상태 관리 그 자체 방법에 따른 차이가 있다기 보다는 성능과 사용이 권장되는 상황에 차이가 있다.
:리덕스는 어떤 컴포넌트가 전역 상태의 특정 값을 의존하게 될 때, 해당 값이 바뀔 때만 리렌더링이 되도록 최적화 되어 있다. 하지만 context API는 성능 최적화가 이루어지지 않아서 컴포넌트가 특정 값에 의존하게 되는 경우, 해당 값과 상관없는 값이 바뀌더라도 컴포넌트 리렌더링이 발생할 수 있다. 그래서 context API는 관심사의 분리가 상당히 중요하다.
=> 전역상태가 고도화, 다양화 되는 경우에는 context API의 사용이 적합하지 않을 수 있다. 단순히 prop-drilling 문제 해결을 위해서는 context API를 사용하고, 앱에 추가적인 기능이 필요하다면 리덕스를 사용한다.

리덕스 파일 설정
redux: 리덕스 관련 코드를 몰아 넣음
config: 리덕스 설정 관련 파일 전부
ㄴconfigStore.js: 중앙 state관리소
modules: state의 그룹(todolist.js등)

=>modules가 state를 만들고, configStore안에 쏙 들어감
**yarn add redux react-redux

기본 코드
configStore.js

//중앙 데이터 관리소

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

const rootReducer = combineReducers({
  //modules에 있는 것들을 넣어줌
});
const store = createStore(rootReducer);

export default store;

index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
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>
);

리덕스 응용 - 카운터1

modules/Counter.js

//초기 상태값 (state)
const initialState = {
  number: 0,
};

//리듀서 : state에 변화를 일으키는 함수
//state를 action의 type에 따라 변경하는 함수
//input(파라미터) : state와 action
//action은 state를 어떻게 할건지 action에 대해 표현하는 객체이다. type과 value를 가짐
//아무것도 없을 때는 default값으로 최초값을 반환해준다는 코드

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

export default counter;

useState대신 전역에서 관리할 수 있도록 state를 만드는 공간이다.
initialState를 선언하고 그 객체 안에 number:0이라는 초기값을 넣어준다.
counter라는 리듀서 함수를 만드는데, 파라미터로 state와 action값을 가진다.
action은 state를 어떻게 할건지에 따라 달라지는 action을 표현하는 객체이다.
type과 payload를 가진다.
switch구문을 써서, action의 type에 따라 달라지는 값을 표현할건데, 아무것도 없을 때는 default값으로 최초값을 반환해준다.

config/configStore.js

//중앙 데이터 관리소

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

const rootReducer = combineReducers({
  counter: counter,
  //counter 로 생략 가능
});
const store = createStore(rootReducer);

export default store;

counter을 import해오고 rootReducer안에 counter을 넣어준다.
이때 counter:counter로 같으면 counter로 생략 가능하다.

App.js

import React from "react";
import { useSelector } from "react-redux";

function App() {
  const data = useSelector((state) => {
    return state;
  });
  return <div>App</div>;
}

export default App;

App.js에서 store의 데이터를 가져오고 싶다. => useSelector hook을 사용한다.
useSelector는 함수를 품고, 그 함수 안에 return값이 들어간다.
여기서 state는 store전체의 데이터를 가리킨다.
따라서 counter리듀서를 counter.js에서 만들고, 그 데이터를 store이 저장하고 있다. 이 데이터를 useSelector를 사용해 App.js로 가져온다.

리덕스 응용 - 카운터2


1.store는 중앙데이터 관리소이다.
:store에서 state를 만들 때 rootReducer를 만들었다.
이 rootReducer안에는 모듈들이 들어갔다.
store안에는 state도 있고 reducer도 있다고 보면 된다.
state는 상태 / reducer는 state를 제어하는 것
(switch문을 통해서 action으로 state를 제어한다.)
2.UI는 컴포넌트이고, App.js이다.
3.dispatch는 온 요청을 수행하며 store에 action객체를 보내준다.
=> UI에서 dispatch로 가면 action이 발생하면서 그 action을 가지고 store을 방문한다. 그럼 action에 타입에 따라 store의 reducer가 state를 제어한다.

modules/Counter.js

//초기 상태값 (state)
const initialState = {
  number: 0,
};

//리듀서 : 함수
//input(파라미터) : state와 action
//action은 state를 어떻게 할건지 action에 대해 표현하는 객체이다. type과 value를 가짐
//아무것도 없을 때는 default값으로 최초값을 반환해준다는 코드
const counter = (state = initialState, action) => {
  switch (action.type) {
    case "plus_one":
      return {
        number: state.number + 1,
      };
    default:
      return state;
  }
};

export default counter;

App.js

import React from "react";
import { useDispatch, useSelector } from "react-redux";

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

  const dispatch = useDispatch();
  return (
    <>
      <div>현재카운트 : {data.number}</div>
      <button
        onClick={() => {
          dispatch({
            type: "plus_one",
          });
        }}
      >
        +
      </button>
    </>
  );
}

export default App;

action을 전달하기 위해 App.js에서 dispatch를 onClick에 활용한다.
type값을 switch구문의 case와 맞춘다.
즉, 액션 type에 따라, case가 'plus_one'일 때, number는 state.number +1이 된다.
그리고 dispatch를 통해 이 action을 전달해주기 위해 onclick에 dispatch를 넣고 type을 맞춰준다.

로직 정리


1.redux파일 안에 config / modules 파일을 나눈다.
우리는 config파일 안에 store을 넣어줄 것이고, modules파일 안에 컴포넌트, 리듀서 등을 넣어줄 것이다.
2.먼저 config파일 안에 configStore.js명을 가진 중앙 저장 관리소 store을 만든다.
3.reducer를 모으기 위한 변수명을 선언하고, 그 객체 안에는 reducer들이 담긴다.
store이라는 변수를 선언해주고, createStore이라는 hook을 활용해 모은 reducer모음집을 담아준다.

4.그런 다음 index.js에 들어가서 리덕스 메소드인 Provider을 활용하여 전체를 감싸고 그 값으로 store을 넣어준다. 그러면 전역에서 활용할 수 있는 데이터가 된다.

5.이제 활용할 reducer들을 만들어보자. 카운트로 +1씩, -1씩 조정하기 위해 초기 값을 만들어놓고, reducer를 선언한다.
6.여기서 파라미터 값은 state와 action이다. action으로 state를 제어한다.
state값에 초기값을 넣어준다.
7.그리고 reducer조건에 switch문을 사용하여 case별로 구분한다. action은 type이라는 성질을 가진다. action의 type에 따라 case를 구분한다.

8.useSelector hook을 활용하여 state값의 counter값을 가져올 수 있도록 한다. (전역 데이터를 어디서든 쓸 수 있도록 가져온다.)
9.그리고 dispatch로 action을 전달해줘야 action의 type에 따라 state를 변경할 수 있으므로 useDispatch()를 선언해주어 import값에 넣어준다.
10.버튼에 onClick을 넣고, dispatch를 활용하여 type을 case에 맞게 지정해주면 끝!
=> 어떠한 type일 때 action을 전달하고, 그 action을 reducer가 실행하면, UI(App.js)에서 보여지게 되는 것

리덕스 심화

dispatch 이름이 바뀐다면? => action value
=> 리듀서 파일에서 변수로 선언해준 후 활용한다.

Counter.js 상단에 추가

export const plus_one = "plus_one";

App.js import로 받아온 뒤 수정 가능

  return (
    <>
      <div>현재카운트 : {data.number}</div>
      <button
        onClick={() => {
          dispatch({
            type: 'plus_one',
          });
        }}
      >
  ---------------변경후--------------    
  return (
    <>
      <div>현재카운트 : {data.number}</div>
      <button
        onClick={() => {
          dispatch({
            type: plus_one,
          });
        }}
      >

액션 객체를 만들어주는 함수? => action creator
:action vaule를 return하는 함수이다.
Counter.js 상단에 추가

export const plusOne = () => {
  return {
    type: plus_one,
  };
};

App.js import로 받아온 뒤 수정 가능

  return (
    <>
      <div>현재카운트 : {data.number}</div>
      <button
        onClick={() => {
          dispatch(plusOne());
        }}

payload

:전달되는 어떠한 실체. 탑승화물과도 같다.
action객체라는 것은 action type을 payload만큼 처리하는 것이다.
우리는 여태까지 '더하여라'라고만 요청을 보냈지만,
'n만큼 더해'라고 요청을 보내기 위해 액션 객체에 같이 담아 보내주는 것을 payload라고 한다.

Counter.js

export const PLUS_N = "PLUS_N";
export const plusN = (payload) => {
  return {
    type: PLUS_N,
    payload: payload,
  };
};

//초기 상태값 (state)
const initialState = {
  number: 0,
};

//리듀서 : 함수
//input(파라미터) : state와 action
//action은 state를 어떻게 할건지 action에 대해 표현하는 객체이다. type과 value를 가짐
//아무것도 없을 때는 default값으로 최초값을 반환해준다는 코드
const counter = (state = initialState, action) => {
  switch (action.type) {
    case plusOne:
      return {
        number: state.number + 1,
      };
    case "minus_one":
      return {
        number: state.number - 1,
      };
    case PLUS_N:
      return {
        number: state.number + action.payload,
      };
    case MINUS_N:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

export default counter;

plusN을 action create하고, 매개변수로 payload를 넣어주어야 한다.
return값으로는 type과 payload가 들어간다.

App.js

function App() {
  const [number, setNumber] = useState(0);
  const data = useSelector((state) => {
    return state.counter;
  });
  
 const dispatch = useDispatch();
  return (
    <>
      <div>현재카운트 : {data.number}</div>
      <div>
        <input
          type="number"
          value={number}
          onChange={(event) => {
            setNumber(+event.target.value);
          }}
        />
      </div>
      <button
        onClick={() => {
          dispatch(plusN(number));
        }}
      >
        +
      </button>

그리고 input값을 받아오기 위해 useState를 먼저 만들고,
onClick에 dispatch를 넣어주고, 거기에 number값을 잡은 plusN을 넣어준다. 그리고 input값을 받아올 때 setNumber은 문자열이 아닌 숫자열로 받아들이기 위해 앞에 +를 붙여주어 숫자로 바꿔준다.

Ducks패턴

:다들 코드가 다르니까 이것을 공통화하기 위해 만든 패턴이다.

  • reducer함수를 export default한다.
  • action create함수들을 export한다.
  • action type은 app, reducer, ACTION_TYPE형태로 작성한다.
    => 모듈 파일 1개에 action type, action creator, reducer가 모두 존재하는 작성 방식이다.
//action type
export const plus_one = "plus_one";
export const PLUS_N = "PLUS_N";
export const MINUS_N = "MINUS_N";

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

export const plusN = (payload) => {
  return {
    type: PLUS_N,
    payload: payload,
  };
};

export const minusN = (payload) => {
  return {
    type: MINUS_N,
    payload: payload,
  };
};

//초기 상태값 (state)
const initialState = {
  number: 0,
};

//reducer : 함수
//input(파라미터) : state와 action
//action은 state를 어떻게 할건지 action에 대해 표현하는 객체이다. type과 value를 가짐
//아무것도 없을 때는 default값으로 최초값을 반환해준다는 코드
const counter = (state = initialState, action) => {
  switch (action.type) {
    case plusOne:
      return {
        number: state.number + 1,
      };
    case "minus_one":
      return {
        number: state.number - 1,
      };
    case PLUS_N:
      return {
        number: state.number + action.payload,
      };
    case MINUS_N:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

export default counter;
profile
Developer ʕ ·ᴥ·ʔ ʕ·ᴥ· ʔ

0개의 댓글