Front-End(Web)/React - 라이브러리들

[Redux] React + Redux App 만들기(생활코딩)

ttaeng_99 2021. 2. 16. 16:16
반응형

이번엔, React 컴포넌트와 Redux의 조합을 공부하려고 한다. (생활코딩 react-redux 과제와 함께)

Global State를 Redux 문법을 통해서 다루는 것 뿐만 아니라, 디렉토리와 파일구조(store, reducer 등)를 배우는 것에도 중점을 둬야겠다.


💜 React Without Redux 💀

이번엔, 아래와 같이 숫자를 수정하고 표시하는데 있어 각각 두 번의 Depth를 거치는 컴포넌트를 React로 만들었다.

// 1. AddNumber.js
const AddNumber = ({ plusNum }) => {
  const [size, setSize] = useState(1);
  return (
    <div>
      <h3>Add Number</h3>
      <input type="text" value={size} onChange={(e) => {
        setSize(Number(e.target.value));
      }} />
      <input type="button" value="+" onClick={() => plusNum(size)}/>
    </div>
  )
}


// 2. AddNumberRoot.js
const AddNumberRoot = ({ plusNum }) => {
  return (
    <div>
      <h2>Add Number Root</h2>
      <AddNumber plusNum={plusNum} />
    </div>
  )
}


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

  const plusNum = (size) => {
    const newNum = number + size
    setNumber(newNum);
  }

  return (
    <div className="App">
      <h1>React Redux: 생활코딩</h1>
      <AddNumberCon plusNum={plusNum} />
      <ShowNumberCon number={number} />
    </div>
  );
}


// 4. ShowNumberRoot.js
const ShowNumberRoot = ({ number }) => {
  return (
    <div>
      <h2>Show Number Root</h2>
      <ShowNumber number={number} />
    </div>
  )
}


// 5. ShowNumber.js
const ShowNumber = ({ number }) => {
  return (
    <div>
      <h2>Show Number</h2>
      <input type="text" value={number} />
    </div>
  )
}

숫자를 변경하고 이것이 표현될때까지 컴포넌트 흐름순으로 코드를 나열했다.

App.js에서 plusNum 함수를 props로 하달하고, AddNumber.js는 size(증가량) 상태를 포함해서 다시 props를 전달해야 한다.

또한, ShowNumber.js로 number 상태를 props로 하달해서 이를 표현시켜야 한다.

 

이 때, 중간의 Container 컴포넌트들은 상태를 활용하지는 않지만, 이를 전달하기 위해 props 수용/하달을 진행하는 문제가 있다.

또한, Depth가 깊어지면 그만큼 props Drilling을 위한 로직들이 추가되어야 한다.


💜 React + Redux 😃

- 설치(npm)

1) Redux 코어 설치: 리덕스 기본 라이브러리 설치

npm install redux

 

2) Redux Toolkit(RTK): Redux 공식추천 방법. 리덕스에 필요한 패키지와 함수를 번들러로 받을 수 있다.

npm install @reduxjs/toolkit

   

 3) React Redux 앱 만들기:  React의 CRA와 Redux Toolkit을 조합한 템플릿으로 초기세팅을 바로 할 수 있는 설치방법이다.

npx create-react-app [디렉토리명] --template redux

 

- 코드수정

1) Store.js : 스토어, 리듀서 함수 제작

import { createStore } from 'redux';

const reducer = (state, action) => {
  if (!state) {
    return {
      number: 0,
    }
  }
  if (action.type === 'INCREMENT') {
    return {...state, number: state.number + action.size}
  }
}

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);


export default store;

먼저, Store.js 파일을 만든다. 여기에는 Redux의 스토어와 리듀서 함수가 들어가 있다.

기존에, App.js에서 관리하던 number 상태와, 숫자를 증가시키는 로직을 이제 여기서 관리한다.

 

2) AddNumber.js : onClick(plusNum 함수) props 불필요, 버튼 클릭시 디스패치 발생

import React, { useState } from 'react';
import store from '../Store';

const AddNumber = () => {
  const [size, setSize] = useState(1);

  return (
    <div>
      <h3>Add Number</h3>
      <input type="text" value={size} onChange={(e) => {
        setSize(Number(e.target.value));
      }} />
      <input type="button" value="+" onClick={() => {
        store.dispatch({
          type: 'INCREMENT',
          size,
        })
      }}/>
    </div>
  )
}

export default AddNumber

 

3) ShowNumber.js : number props 불필요, store.getState() 로 상태 불러오기

// ShowNumber.js

import React, { useState } from 'react';
import store from '../Store';

const ShowNumber = () => {
  const [number, setNumber] = useState(0);

  store.subscribe(() => {
    setNumber(store.getState().number)
  })
  
  return (
    <div>
      <h2>Show Number</h2>
      <input type="text" value={number} />
    </div>
  )
}

export default ShowNumber

 

4) AddNumberRoot.js, ShowNumberRoot.js 등 컴포넌트들의 props 전달이 불필요해짐

 

 

- Container 컴포넌트 & Presentationl 컴포넌트

위 AddNumber.js 컴포넌트를 예시로 보자. 이 컴포넌트는 재활용이 까다롭다. (리덕스 스토어에 종속되어 있기 때문)

만약, 이 컴포넌트의 UI를 재활용하려고 하는데, 내부의 값은 Global State와 무관할 경우 코드를 복붙해야만 할 것이다.

 

재활용성 향상을 위해, 우리는 컴포넌트를 기능적으로 분리한다.

  • Container 컴포넌트: Redux 관련기능만을 담당한다. 부모 컴포넌트로서 Presentational 을 감싸며, props를 내려준다.
  • Presentational 컴포넌트: Redux 기능은 배제되고 UI만 담당한다. 자식 컴포넌트로 Container의 props 값을 활용한다.
// Container: AddNumberCon.js
import React from 'react'
import AddNumber from '../components/AddNumber';
import store from '../Store';

const AddNumberCon = () => {
  return (
    <AddNumber onClick={(size) => {
      store.dispatch({
        type: 'INCREMENT',
        size,
      })
    }}/>
  )
}
// Presentational: AddNumber.js
import React, { useState } from 'react';

const AddNumber = ({ onClick }) => {
  const [size, setSize] = useState(1);

  return (
    <div>
      <h3>Add Number</h3>
      <input type="text" value={size} onChange={(e) => {
        setSize(Number(e.target.value));
      }} />
      <input type="button" value="+" onClick={() => onClick(size)}/>
    </div>
  )
}

Container인 <AddNumberCon> 를 만든다. Redux와 관련된 디스패치 함수가 포함되며, props로 <AddNumber>에 내려준다.

<AddNumber> 는 Presentational 을 맡는다. props로 받은 onClick 함수(디스패치)에, 현재 size값만 넘겨주면 된다. (No Redux)


분명, Redux 를 통한 상태관리와 props Drilling 제거는 좋은 현상이다.

하지만, 마지막처럼 우리는 컴포넌트의 기능분리를 위해 Container & Presentational 로 각각 나눠주어야 했다.

또한, 규모가 커질수록 이 역시 이중작업이 되며, 다루는 상태가 많아질수록 Container가 내리는 props도 증가한다.

 

이러한 한계에 다시 봉착하면서, React와 Redux가 각각의 역할에 좀 더 충실하도록 해주는 것이 바로 react-redux 라이브러리다.

다음 포스팅에선, 이 react-redux에 대해 좀 더 다뤄보겠다.

반응형