[React] 15. React-Redux 예제 (Connect)

dev·2020년 10월 21일
0

React

목록 보기
15/21

예제는 class 컴포넌트를 사용한 예제입니다.

전 시간에 했던 예제를 Connect()항수를 사용하기 전에
일단 프레젠터널 컴포넌트와 컨테이너 컴포넌트로 분리를 해보겠습니다.

지금 현재 AddNumber.jsx파일을 보면

import React, { Component } from "react";
import store from "../store";
export default class AddNumber extends Component {
  state = { size: 1 };
  render() {
    return (
      <div>
        <h1>Add Number</h1>
        <input
          type="button"
          value="+"
          onClick={(e) => {
            store.dispatch({ type: "INCREMENT", size: this.state.size });
          }}
</input>
        <input
          type="text"
          value={this.state.size}
          onChange={(e) => {
            if (e.target.value === "" || e.target.value === null) {
              this.setState({ size: parseInt(0) });
            } else {
              this.setState({ size: parseInt(e.target.value) });
            }
          }}
</input>
      </div>
    );
  }
}

이렇게 한 화면에서 화면을 보여주고, 기능을 처리하다보면 복잡해지고, 가독성 역시 떨이지게 되는데 즉 유지보수 하기가 힘들어집니다.
그래서 그래서 Redux 를 개발한 Dan abramov 는 재사용성과 유지보수성에 초점을 두어 이러한 패턴을 발견하고 이를 사용하길 권하고 있습니다.

다음 2가지의 컴포넌트를 살펴 보겠습니다.
resentational Component 와 Container Component 의 각각의 사용 목적과 개념은 다음과 같다.

프레젠테이션 컴포넌트 (Presentational Component)

  1. 어떻게 보여지는 지를 책임진다.
  2. 내부에 Presentational Component 와 Container 컴포넌트 모두를 가질 수 있고, 대게 DOM 마크업 과 자체 스타일을 가진다.
  3. this.props.children 을 통해 다른 컴포넌트 를 포함 하게끔 만들 수 있다.
  4. 어플리케이션의 나머지 부분에 대해 아무런 의존성을 가지지 않는다. (예를 들면 Flux 의 Action 이나 Stroe) 즉, 단독적인 Component 가 된다.
  5. 데이터를 불러오거나 변경하는 작업은 Presentational Component 에서 작성하지 않는다.
  6. 데이터 및 데이터와 관련된 Callback 은 props 를 통해서 받는 작업만 한다.
  7. 상태(state) 는 UI 상태를 관리하기 위해서만 갖게된다.
  8. state, LifeCycle, 성능 최적화가 필요없는 경우라면 Functional Component 로 작성된다.

컨테이너 컴포넌트 (Container Component)

  1. 어떻게 동작해야 할지를 책임진다.
  2. 내부에 Presentational Component 와 Container 컴포넌트 모두를 가질 수 있지만, 대게 전체를 감싸는 div를 제외하고 자체적인 DOM 마크업이나 스타일을 갖고 있지 않다.
  3. 데이터 및 데이터와 관련된 동작을 다른 Presentational Component 와 Container Component 에게 제공한다.
  4. Flux 의 Action 을 호출하는 작업을 Container Component 에서 작성하며, 이 Callback 들은 다른 Presentational Component 에게 넘겨준다.
  5. 주로 데이터 저장소로 활용되며 상태(state) 를 갖고 있는 경우가 많다.
  6. 직접 작성되기 보다는 HOC(Higher-Order Components) 로 부터 생성되는 경우가 많다.

이렇게 프레젠테이션 컴포넌트는 화면에 보여주기만을
프레젠테이션 컴포넌트의 기능을 따로 빼서 컨테이너 컴포넌트에 만들어서 분리를 시켜 재사용성을 하게끔 만들어줍니다.

이제 컨테이너 컴포넌트를 만들어보겠습니다.
src 폴더 안에 containers 폴더를 만들고 AddNumber.jsx, DisplayNumber.jsx파일을 만들어줍니다. 다음으로 containers폴더 안에 AddNumber.jsx를 작성하겠습니다.

containers/AddNumber.jsx

import React, { Component } from "react";
import store from "../store";
export default class extends Component {
render() {
return (
<AddNumber
onClick={(size) => {
store.dispatch({ type: "INCREMENT", size: size });
}}

  ></AddNumber>
);

}
}

코드를 보면 components/AddNumber.jsx에서 클릭 이벤트 발생했을때 store.dispatch() 실행하는 부분을 옮겨왔습니다. size은 받아온 인자값이구요.
onClick 이벤트 함수를 components/AddNumber.jsx에 속성으로 보내줘서 호출할 수 있게 해줍니다.

다음으로 components/AdNumber.jsx를 보겠습니다.

components/AddNumber.jsx

import React, { Component } from "react";
import store from "../store";
export default class AddNumber extends Component {
  state = { size: 1 };
  render() {
    return (
      <div>
        <h1>Add Number</h1>
        <input
          type="button"
          value="+"
          onClick={(e) => {
			this.props.onClick(this.state.size);
          }}
</input>
        <input
          type="text"
          value={this.state.size}
          onChange={(e) => {
            if (e.target.value === "" || e.target.value === null) {
              this.setState({ size: parseInt(0) });
            } else {
              this.setState({ size: parseInt(e.target.value) });
            }
          }}
</input>
      </div>
    );
  }
}

코드를 보면 클릭이벤트 함수에서 props로 받은 onClick()함수에 값을 담아 호출을 해줍니다.
이렇게 기능을 분리하여 componentes/AddNumber.jsx는 프레젠테이션 컴포넌트 역할을 containers/AddNumber.jsx는 컨테이너 컴포넌트 역할을 할 수 있게 하였습니다.

AddNumberRoot.jsx

이제 AddNumberRoot.jsx에서 containers/AddNumber.jsx를 부르게 경로를 변경해주겠습니다.

import React, { Component } from "react";
import AddNumber from "../containers/AddNumber"; // 경로변경
export default class AddNumberRoot extends Component {
  render() {
    return (
      <div>
        <h1>Add Number Root</h1>
        <AddNumber></AddNumber>
      </div>
    );
  }
}

이와 같이 DisplayNumber도 변경해주도록하겠습니다.

containers/DisplayNumber.jsx

import DisplayNumber from "../components/DisplayNumber";
import store from "../store";
import React, { Component } from "react";
export default class extends Component {
  state = { number: store.getState().number };
  constructor(props) {
    super(props);
    store.subscribe(() => {
      this.setState({ number: store.getState().number });
    });
  }
  render() {
    return (
      <div>
        <DisplayNumber number={this.state.number}></DisplayNumber>
      </div>
    );
  }
}

components/DisplayNumber.jsx

import React, { Component } from "react";
class DisplayNumber extends Component {
  render() {
    return (
      <div>
        <h1>Display Number</h1>
        {this.props.sum}
        <input type="text" value={this.props.number} readOnly></input>
      </div>
    );
  }
}
export default DisplayNumber;

DisplayNumberRoot.jsx

import React, { Component } from "react";
import DisplayNumber from "../containers/DisplayNumber"; //경로변경
class DisplayNumberRoot extends Component {
  render() {
    return (
      <div>
        <h1>Display Number Root</h1>
        <DisplayNumber sum="합 :"></DisplayNumber>
      </div>
    );
  }
}
export default DisplayNumberRoot;

이렇게 코드를 작성하고 실행을 하면 전과 동일하게 작동을 하는데, 컴포넌트를 분리함으로써 재사용성과 유지보수성이 높아지게 리팩토리을 했습니다.

이제 해당 코드를 가지고 connect()함수를 써서 변경해보도록하겠습니다.

connect

**connect함수는 컨테이너 컴포넌트를 만드는 또 다른 방법이며,
클래스형 컴포넌트로 작성을 하게 되는 경우에는 Hooks 를 사용하지 못하기 때문에 connect 함수를 사용하셔야 됩니다.

connect(1,2)(3)함수는 connect(mapReduxStateToReactProps, mapDeduxDispatchtoReactProps)(전달받을 컴포넌트) 이렇게 되어있습니다.
(안에 들어가는 함수명은 자기 마음대로입니다.)
1,2를 먼저 보기전에 3번부터 보겠습니다.
3번은 1,2번을 보내줄 하위컴포넌트를 작성해주면 되는 부분이라 이렇게 얘기하고 코드를 보면 쉽게 이해가 갈겁니다.

1번 : mapReduxStateToReactProps를 보면 redux store가 변경될 때마다 호출이 되며, redux state값을 react props로 mapping 해준다는 뜻입니다.
mapReduxStateToReactProps의 형태는

function mapReduxStateToReactProps(state){
return { id : state.id};
}
인자값으로 받은 state는 redux store의 state값을 공급해주고. return값으로 react props가 되는겁니다. 3번(하위컴포넌트)에서 사용하게 될 props.state가 되는거죠

2번 : mapDeduxDispatchtoReactProps는 3번에서 사용할 이벤트라고 생각하면 됩니다.
형태는

function mapDispatchToProps(dispatch) {
return onClick: function (size) {
dispatch({ type: "INCREMENT", size: size });
}
}
이렇게 mapDispatchToProps는 인자값으로 store.dispatch() api를 받아서 사용할 수 있게 해줍니다. return 값은 3번(하위컴포넌트)에서 사용하게 될 props.onClick이 됩니다.

자 이제 적용해보도록 하겠습니다.

아까 만들어뒀던 컨테이너 컴포넌트 DisplayNumber.jsx를 보겠습니다.
기존에 있던 소스와 비교해보면 좀더 쉽게 이해할 수 있습니다.

containers/DisplayNumber.jsx

import DisplayNumber from "../components/DisplayNumber";
import { connect } from "react-redux"; // connect 추가
function mapReduxStateToReactProps(state) {
  //인자값으로 redux store의 state값을 공급해준다.
  return {
    number: state.number,
  };
  //mapReduxStateToReactProps 하는 일이 아래 소스와 동일
  /*
  state = { number: store.getState().number };
  store.subscribe(() => {
      this.setState({ number: store.getState().number });
    });
  number={this.state.number}
  와 동일한 내용이다.
  */
}
function mapDeduxDispatchtoReactProps() {
  // AddDisplay에서는 필요없음
  return {};
}
//connect()함수 사용 import시킨 components/DisplayNumber.jsx가 하위컴포넌트
export default connect(
  mapReduxStateToReactProps,
  mapDeduxDispatchtoReactProps
)(DisplayNumber);

나머지 AddNumber 컨테이너 컴포넌트도 변경해보도록 하겠습니다.

containers/AddNumber.jsx

import AddNumber from "../components/AddNumber";
import { connect } from "react-redux";
function mapReduxStateToReactProps(state) {
	return {};
}
function mapDispatchToProps(dispatch) {
  // 인자값으로 store.dispatch() api를 공급해준다.
  return {
    onClick: function (size) {
      dispatch({ type: "INCREMENT", size: size });
    },
  };
}
export default connect(mapReduxStateToReactProps, mapDispatchToProps)(AddNumber);
//AddNumber에서는 이벤트를 넘겨준 것을 볼 수 있습니다.

이와 같이 코드를 작성하고 실행을 했을때 전과 동일하게 작동하는 것을 보실 수 있습니다.

현재 예제는 class 컴포넌트를 사용한 예제입니다.

profile
studying

0개의 댓글