Learning React(14. React 와 Redux)

min seung moon·2021년 8월 18일
0

Learning React

목록 보기
15/15

1. React와 Redux

01. React의 데이터 전달 계층 구도

  • 앱의 계층 구도
  • 컴포넌트가 상태를 관리하고 속성의 형태로 전달하는 책임을 맡는다
  • 이상적인 구성이라면 아래와 같이 각 컴포넌트가 필요로 하는 데이터가 부모에서 자식으로 차례차례 흐른다

    그러나 단순 시나리오가 아니라면 불행히도 현실은 그렇게 녹녹치 않다
    • 보통의 앱에서는 상태의 생성, 처리, 전달 과정이 아주 많이 일어나기 때문이다
    • 어떤 컴포넌트는 상태 변경을 일으킨다, 또 다른 어떤 컴포넌트는 그 상태에 반응한다
    • 상태 변경과 관련된 속성은 상태 데이터를 필요로 하는 컴포넌트에 도달하기까지 계층도 아래쪽으로 내려가기도 하지만 또한 위로 올라가기도 한다
  • 컴포넌트들 사이에 무분별하게 데이터를 주고 받으면서 발생할 수 있는 몇가지 문제점을 알아야 할 필요가 있다
      1. 의존성은 코드 관리를 어렵게 만든다. 리액트 목표 중 하나는 스파게티와 같은 복잡한 의존성을 제거하는 것이다, 앱 주변에 데이터가 떠돌게 방치하는 일은 결국 우리가 도망가고 싶은 상황을 만드는 일과 같다
      1. 상태가 변하거나 속성이 전달될 대 마다 관련된 모든 컴포넌트가 매번 다시 렌더링 된다, 이는 현재 상태에 부합하는 UI의 동기화를 보장하기 위한 자연스러운 동작이다, 그러나 이전에 말했듯 단순히 부모에서 자식으로 값이 전달될 때 많은 컴포넌트들이 불필요한 렌더링을 하게 된다, 이를 방지하기 위해 shouldComponentUpdate 메서드를 재정의하거나 PureComponent를 사용하는 전략이다, 그러나 두 방법 모두 데이터가 증가함에 따라 동기화 작업이 번거롭게 된다는 단점이 있다
      1. 컴포넌트 계층도 UI에 대한 것이지, 데이터에 대한 것이 아니다 컴포넌트를 배치하고 끼워 넣는 작업은 이미 UI를 작고 관리가 용이하게 분리시키는 좋은 방법이다, 이는 올바른 접근 방법이 맞지만, 상태를 변경시키는 컴포넌트와 그 상태에 반응해야 할 컴포넌트가 직계존비속 관계가 아닌 경우도 많다, 이 경우 위 2번과 마찬가지로 속성이 아주 멀리 전달돼야 하기도 하며, 게다가 종종 한번의 상태 변화에 다수의 전달 작업이 수행되고 한다

02. Redux의 데이터 계층 구도

  • 위의 문제들을 해결할 수 있는게 리덕스다, 현재로서는 완벽하게는 아니지만, 거의 그에 가깝게 말이다, 리덕스를 사용하면 각 컴포넌트에 걸쳐 분산돼 있는 애플리케이션의 상태를 모두 데이터 스토어에 저장할 수 있게 해준다
  • 이 접근 방법은 여러 문제를 해결해준다, 예건대 어떤 데이터를 앱의 서로 다른 부분들에 공유시키기 위해 컴포넌트 계층의 위아래로 전달시키지 않아도 된다
  • 리덕스를 사용하면 변경을 촉발시킬 수 있으며, 오직 직접적으로 영향을 받는 컴포너늩들만 개입할 수 있다, 이는 불필요한 렌더링을 없앨 목적으로 해당 컴포넌트에만 데이터가 전달되게 하는 관리 작업의 과부하를 줄여 준다

03. Redux의 아키텍처 관점

  • 스토어를 제외하면 앞으로도 여전히 액션리듀서, 그리고 리덕스 파티에 참여하는 다른 조각들에 대해 작업할 것이다, 유일하게 바뀐 사항은 이번에는 리액트로 앱을 만들 것이라는 점이다

04. 리덕스를 이용한 리액트 상태 관리

    1. 앱이 리덕스 스토어를 참조할 수 있게 한다
    1. 스토어의 데이터를 필요로 하는 컴포넌트에 액션 생성자, 디스패치 함수, 상태를 속성으로서 매핑

-1. 리액트와 리덕스 합치기

  • 이번에 Counter 앱을 만들어서 Counter 컴포넌트 안에 상태 객체를 만들고, 눌러진 버튼에 따라 값이 증가되거나 감소되는 변수를 하나를 두면 될 것이다
  • 위 계층구도에서 리덕스를 합류시키면 컴포넌트의 배열이 다소 특이하게 바뀐다
  • 초록색 아이템들은 리덕스가 결합된 후에 새로 추가한 것이다
      1. 첫 번째 단계는 리덕스 스토어로 접근하는 방법을 제공하는 일이며, 이는 Provider Component가 담당
      1. 두 번째 단계는 어떤 컴포넌트든 디스패치와 액션에 접근 가능하게 하는 일이며, 이는 Connect Component가 담당
    • Provider Component는 리액트 앱에서 리덕스를 사용하기 위한 관문 역할을 한다
      • 스토어에 대한 참조를 저장하며 앱 안의 모든 컴포넌트에게 스토어로의 접근 방법을 보장
      • 컴포넌트 계층도에서 최상위 컴포넌트여야 가능하다
      • 그래야만 리덕스의 기능을 전체 앱에 쉽게 전파 할 수 있기 때문이다
    • Connect Component는 기능을 담당하는데, Connect는 전통적인 의미의 컴포넌트가 아닌 고차원 컴포넌트(Higher Order Component)이기 때문이다
      - 흔히 줄여서 HOC라고 부르며, HOC는 기존 컴포넌트를 래핑하고 HOC 고유의 기능을 추가로 주입함으로써 그 컴포넌트의 기능성을 확장시키는 일관된 방법을 제공
      - 말하자면 extends 키워드를 사용해 ES6 클래스로 하는 일과 비슷한, 리액트 만의 방식이라고 생각하면 좋다
      - HOC 덕분에 Counter 컴포넌트가 리덕스 스토어와의 작업을 위해 액션디스패치에 접근할 수 있게 된다

-2. 시작하기

  • create-react-app reduxcounter
  • npm install redux
    • 리덕스 라이브러리
    • 애플리케이션 상태 관리를 위해 리덕스가 제공하는 기본적인 빌딩 블록을 앱에서 사용할 수 있게 해준다
  • npm install react-redux
    • redux 라이브러리에 대한 리액트의 의존성 패키지
  • 언제나 하듯이 src & public 폴더를 깔끔하게 해준다

-3. public / index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux Counter</title>
</head>
<body>
  <div id="container"></div>
</body>
</html>

-4. src / index.js

  • 리듀서를 인자로 받는 createStore 메서드를 사용해 리덕스 스토어를 초기화 해주었다
  • 리듀서는 counter 변수에 의해 참조되며. import 구문을 보면 reducer.js라는 파일에 정의돼 있어야 함을 알 수 있다
  • 스토어를 만든 다음에 이를 Provider Component의 속성으로 제공
  • Priver는 앱의 가장 바깥쪽 컴포넌트로서, 어떤 컴포넌트든 리덕스 스토어와 그 관련 기능에 접근할 수 있도록 보장해준다
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import { Provider } from 'react-redux';
import counter from './reducer';
import App from './App';
import './index.css';

// 스토어
var store = createStore(counter);

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>
  , document.querySelector("#container")
);

-5. src / reducer.js

  • counter 메서드가 하는일을 보면 먼저 상태(state)가 비어있다면 0으로 초기화되는 count 변수가 있다
  • increas, decreas라는 두 가지 액션 유형을 다루며, 액션 유형이 increase라면 count 값을 1 증가, decrease라면 count 값을 1 감소 해주는 동작을 한다
// 리듀서
function counter(state, action) {
  if (state === undefined) {
    return { count: 0 };
  }

  var count = state.count;

  switch (action.type) {
    case "increase":
      return { count: count + 1 };
    case "decrease":
      return { count: count - 1 };
    default:
      return state;
  }
}

export default counter;

-6. src / App.js

  • 이 코드의 주 목적은 모든 리덕스에 특정적인 후크를 리액트에서 사용할 수 있는 무언가로 바꾸는 것이다
    • 더 정확히는 모든 리덕스 후크를 mapStateToPropsmapDispatchToProps라는 두 함수를 통해 쉽게 소비할 수 있는 속성의 형태로 바꿔 제공한다는 의미다
  • mapStateToProps
    • 이 함수는 모든 스토어 갱신 작업을 구독하므로, 스토어에 이떤 변경이 일어나도 호출된다
    • 그러면 컴포넌트의 속성으로 전달할 스토어 데이터를 담는 객체를 리턴
  • mapDispatchToProps
    • mapDispatchToProps에서는 액션 생성자의 일을 한다
    • 이 함수는 컴포넌트가 스토어를 변경시키는 액션에 디스패치 가능한 두 함수의 이름을 담은 객체를 리턴한다
    • increaseCount 함수는 increase 유형의 액션에 디스패치 해준다
    • decreaseCount 함수는 decrease 유형의 액션에 디스패치 해준다
  • connect
    • 이 함수는 예기했던 Connect HOC를 생성시켜준다
    • mapStateToProps와 mapDispatchToProps함수를 인자로 받으며, 이를 모두 Counter 컴포넌트로 전달한다
    • 보다시피 Counter 컴포넌트 increaseCount, decreaseCount, countValue를 사용할 수 잇게 된다
    • 한가지 이상한 점은 render 함수나 그와 비슷한 어떤 것도 없다는 사실이다
    • 이는 리액트와 HOC가 자동으로 모든 사항을 처리해주기 때문이다
import { connect } from "react-redux";
import Counter from "./Counter";

// 리덕스 상태를 컴포넌트 속성에 매핑
function mapStateToProps(state) {
  return {
    countValue: state.count,
  };
}

// 액션
var increaseAction = {type : "increase"};
var decreaseAction = {type : "decrease"};

// 리덕스 액션을 컴포넌트 속성에 매핑
function mapDispatchToProps(dispatch) {
  return {
    increaseCount : function() {
      return dispatch(increaseAction);
    },
    decreaseCount : function() {
      return dispatch(decreaseAction);
    }
  };
}

// HOC
var connectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

export default connectedComponent;

-7. src / Counter.js

import React, {Component} from 'react';

class Counter extends Component {
  render() {
    return(
      <div className="contaner">
        <button className="buttons" onClick={this.props.decreaseCount}>
          -
        </button>
        <span>{this.props.countValue}</span>
        <button className="buttons" onClick={this.props.increaseCount}>
          +
        </button>
      </div>
    )
  }
}

export default Counter;

-8. src / index.css

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  display: flex;
  justify-content: center;
  background-color: #8E7C93;
}

.contaner {
  background-color: #FFF;
  margin: 100px;
  padding: 10px;
  border-radius: 3px;
  width: 200px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.buttons {
  background-color: transparent;
  border: none;
  font-size: 16px;
  font-weight: bold;
  border-radius: 3px;
  transition: all .15s ease-in;
  cursor: pointer;
}

.buttons:hover:nth-child(1) {
  background-color: #F45B69;
}

.buttons:hover:nth-child(3) {
  background-color: #C0DFA1;
}



profile
아직까지는 코린이!

0개의 댓글

관련 채용 정보