리액트(React)

리액트(React) - react-redux

백코딩 2023. 9. 25. 10:21
728x90

리덕스라는 도구는 리액트에 종속된 도구가 아니라 독립적인 도구입니다. 

import React,{ useState } from 'react';
import './style.css';

export default function App() {
  const [number, setNumber] = useState(1);

  return (
    <div id="container">
      <h1>Root</h1>
      <div id='grid'>
        <Left1 number={number}></Left1>
        <Right1 onIncrease={() => {
          setNumber(number + 1);
        }}></Right1>
      </div>
    </div>
  );
}

function Left1(props) {
  return (
    <div>
      <h1>Left1 :{props.number}</h1>
      <Left2 number={props.number}></Left2>
    </div>
  );
}

function Left2(props) {
  return (
    <div>
      <h1>Left2 : {props.number}</h1>
      <Left3 number={props.number}></Left3>
    </div>
  );
}

function Left3(props) {
  return (
    <div>
      <h1>Left3 : {props.number}</h1>
    </div>
  );
}


function Right1(props) {
  return (
    <div>
      <h1>Right1</h1>
      <Right2 onIncrease={() => {
        props.onIncrease();
      }}></Right2>
    </div>
  );
}

function Right2(props) {
  return (
    <div>
      <h1>Right2</h1>
      <Right3 onIncrease={() => {
        props.onIncrease();
      }}></Right3>
    </div>
  );
}

function Right3(props) {
  return (
    <div>
      <h1>Right3</h1>
      <input type='button' value="+" onClick={() => props.onIncrease()}/>
    </div>
  );
}

값을 바꾸고 싶을 때 몇 개의 컴포넌트가 서로 부모 자식 관계로 끝없이 이어져 있고 이런 식으로 하게 되면 컴포넌트가 4개가 아닌 1억 개라면 절망적이겠죠. 비유하자면 props를 통해 유선으로 연결된 거라고도 할 수 있어요. 그럼 Left의 말단에 있는 스테이트와, Right의 말단에 있는 이벤트를 무선으로 연결하면 얼마나 좋을까요 이 꿈을 실현해 주는 환상적인 도구가 리덕스입니다.

 

리액트 리덕스(React Redux)라는 도구를 이용해서 앞서 만든 예제에 리액트 리덕스를 추가해보겠습니다. 리덕스는 독립적인 도구입니다. 독립적인 리덕스와 또 하나의 독립적인 도구인 리액ㅌ를 서로 연결하는 도구가 바로 리액트 리덕스라는 도구입니다.

npm install redux react-redux

스토어를 만들어보겠습니다. 이렇게 만든 스토어는 수정하면 안 되기 때문에 상수로 선언하고 createStore()로 스토어 생성합니다.

import { createStore } from "redux";

const store = createStore();

스토어를 생성할 때 반드시 주입해야 하는 것이 리듀서(reducer)입니다. 리듀서는 스토어에 있는 스테이트를 어떻게 바꿀 것인지 결정한는 역할을 합니다. 그래서 reducer는 두 개의 파라미터를 받습니다. 첫 번째는 현재의 스테이트 값이 currentState이고, 두 번째는 현재의 값을 어떻게 바꿀 것인지에 대한 요청인 action입니다. 이렇게 받은 값을 리턴하며 리턴한 값이 새로운 스테이트의 값이 됩니다.

 

const store = createStore(reducer);

function reducer(currentState, action){
  if(currentState === undefined){
    return {
      number : 1
    };
  }

  const newState = {...currentState};
  
  return newState;
}

이때 리덕스는 각각의 스테이트의 변환를 불변하게 유지해야 하는데, 그러기 위한 방법을 새로운 스테이트를 만들 때 과거의 스테이트를 복제하는 것입니다. 어떻게 하던 현재의 객체가 복제돼서 복제본이 만들어지고, 그 복제본을 수정하면 불변성을 유지할 수 있습니다. 그리고 이렇게 변화시킨 스테이트를 리턴하는 것입니다.  그런데 currentState의 값이 undefined라면 그것 무슨 뜻일까요? 스테이트가 정의되지 않았다는 것이므로 기본 스테이트 값을 리턴함으로써 기본값을 설정할 수 있습니다. 즉, 코드를 보면 number 스테이트의 기본값이 1이 됩니다.

 

리액트 리덕스에는 4인방이 있습니다. 첫 번째는 Provider 컴포넌트입니다. 두 번째는 useSelector로, 어떤 스테이트 값을 쓰고 싶은지 선택하는 것입니다. 세 번째 useDispatch로서 스테이트 값을 변경할 때 사용합니다. 마지막으로 connet가 있습니다. 모두 리액트 리덕스에서 제공하는 기능입니다.

 

import { createStore } from "redux";
import { Provider, useSelector, useDispatch, connect } from "react-redux";

Provider는 스테이트를 어떤 컴포넌트들에 제공한 것인지 가장 바깥쪽에 있는 울타리를 정하는 역할을 합니다.

      <div id='grid'>
        <Provider store={store}>
          <Left1></Left1>
          <Right1></Right1>
        </Provider>
      </div>

 

무선으로 연결하기 위해 useSelection를 사용합니다.

function Left3(props) {
  const number = useSelector((state) => state.number);

  return (
    <div>
      <h1>Left3 : {number}</h1>
    </div>
  );
}

전역적이라고도 할 수 있는 store에 저장된 number 스트이트와 말단에 있는 컴포넌트의 number를 무선으로 연결한 것입니다.

 

디스패치는 useDispatch를 이용해 가져올 수 있습니다. 이렇게 가져온 디스패치 type 값으로 PLUS라는 액션을 전달합니다.

const store = createStore(reducer);

function reducer(currentState, action){
  if(currentState === undefined){
    return {
      number : 1
    };
  }

  const newState = {...currentState};

  if(action.type ==='PLUS'){
    newState.number++;
  }

  return newState;
}
function Right3(props) {
  const dispatch  = useDispatch();
  return (
    <div>
      <h1>Right3</h1>
      <input type='button' value="+" onClick={() => {
        dispatch({type: "PLUS"})}}/>
    </div>
  );
}

디스패치는 useDispatch를 이용해 가져올 수 있습니다. 이렇게 가져온 디스패치에 type 값으로 PLUS라는 액션을 전달합니다.

이것이 리덕스를 사용했을 때의 환상적인 효과 중 하나고, 이를 쉽게 만드러주는 도구가 리액트 리덕스인 것입니다. 또 한가지 흥미로운 점은 Left3에 console.log('3')을 추가하고, Left2에 console.log('2)를 추가한 다음 [+] 버튼을 눌러보면 콘솔에 3만 출력되는 모습을 볼 수 있습니다.

 

즉, 스테이트를 사용하는 number만 값이 바뀌고, 그 부모는 다시 렌더링되지 않기 때문에 퍼포먼스 측면에서도 상당한 이점이 있습니다.

 

import React,{ useState } from 'react';
import './style.css';
import { createStore } from "redux";
import { Provider, useSelector, useDispatch, connect } from "react-redux";

const store = createStore(reducer);

function reducer(currentState, action){
  if(currentState === undefined){
    return {
      number : 1
    };
  }

  const newState = {...currentState};

  if(action.type ==='PLUS'){
    newState.number++;
  }

  return newState;
}

export default function App() {

  return (
    <div id="container">
      <h1>Root</h1>
      <div id='grid'>
        <Provider store={store}>
          <Left1></Left1>
          <Right1></Right1>
        </Provider>
      </div>
    </div>
  );
}

function Left1(props) {
  return (
    <div>
      <h1>Left1</h1>
      <Left2></Left2>
    </div>
  );
}

function Left2(props) {
  console.log('2');
  return (
    <div>
      <h1>Left2</h1>
      <Left3></Left3>
    </div>
  );
}

function Left3(props) {
  const number = useSelector((state) => state.number);
  console.log('3');
  return (
    <div>
      <h1>Left3 : {number}</h1>
    </div>
  );
}


function Right1(props) {
  return (
    <div>
      <h1>Right1</h1>
      <Right2></Right2>
    </div>
  );
}

function Right2(props) {
  return (
    <div>
      <h1>Right2</h1>
      <Right3></Right3>
    </div>
  );
}

function Right3(props) {
  const dispatch  = useDispatch();
  return (
    <div>
      <h1>Right3</h1>
      <input type='button' value="+" onClick={() => {
        dispatch({type: "PLUS"})}}/>
    </div>
  );
}

 

connect() 추가

import React from 'react';
import './style.css';
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

const store = createStore(reducer);

function reducer(currentState, action) {
  if (currentState === undefined) {
    return {
      number: 1
    };
  }

  const newState = { ...currentState };

  if (action.type === 'PLUS') {
    newState.number++;
  }

  return newState;
}

export default function App() {
  return (
    <div id="container">
      <h1>Root</h1>
      <div id='grid'>
        <Provider store={store}>
          <Left1></Left1>
          <Right1></Right1>
        </Provider>
      </div>
    </div>
  );
}

function Left1(props) {
  return (
    <div>
      <h1>Left1</h1>
      <ConnectedLeft2></ConnectedLeft2>
    </div>
  );
}

function Left2(props) {
  console.log('2');
  return (
    <div>
      <h1>Left2</h1>
      <ConnectedLeft3></ConnectedLeft3>
    </div>
  );
}

function Left3(props) {
  console.log('3');
  return (
    <div>
      <h1>Left3 : {props.number}</h1>
    </div>
  );
}

const mapStateToPropsLeft3 = (state) => {
  return {
    number: state.number
  };
};

const ConnectedLeft3 = connect(mapStateToPropsLeft3)(Left3);
const ConnectedLeft2 = connect()(Left2);  // Connecting it so it's consistent, even if we don't use Redux props/actions here

function Right1(props) {
  return (
    <div>
      <h1>Right1</h1>
      <ConnectedRight2></ConnectedRight2>
    </div>
  );
}

function Right2(props) {
  return (
    <div>
      <h1>Right2</h1>
      <ConnectedRight3></ConnectedRight3>
    </div>
  );
}

function Right3(props) {
  return (
    <div>
      <h1>Right3</h1>
      <input type='button' value="+" onClick={props.increaseNumber} />
    </div>
  );
}

const mapDispatchToPropsRight3 = (dispatch) => {
  return {
    increaseNumber: () => dispatch({ type: "PLUS" })
  };
};

const ConnectedRight3 = connect(null, mapDispatchToPropsRight3)(Right3);
const ConnectedRight2 = connect()(Right2);  // Connecting it so it's consistent, even if we don't use Redux props/actions here
728x90