React Redux 기본학습 -4. React-Redux 적용

React Redux 기본학습 -4. React-Redux 적용

생활코딩의 React Redux 강좌를 보면서 학습한 내용 정리용.

생활코딩 - React Redux 적용

React Redux 의 적용

실습 화면은 Redux를 사용하지 않았던 것과 동일. 0082-001.png

0. react-redux 를 app 전체에서 사용할 수 있도록 설정함 (/index.js)

1-1. components/AddNumber.jsx(this.props.onClick 이벤트로 입력받은 number 를 return 함.

1-2. containers/AddNumber.jsx(Container component) (import {connect} from ‘react-redux’; 를 선언하고 connect에 store 에 상태를 전달할 수 있도록 설정한다.)

2. components/AddNumberRoot.jsx(아무것도 안해도 됨)

3. Root(/app.js) (아무것도 안해도 됨)

4. components/DisplayNumberRoot.jsx (아무것도 안함)

5-1. containers/DisplayNumber.jsx (import {connect} from ‘react-redux’; 를 선언하고 connect에 store 에 이벤트를 전달할 수 있도록 설정한다.)

5-2. components/DisplayNumber.jsx (store의 값이 바뀌든 말든 얘는 5-1.이 필요하면 새로 렌더링하고, 5-1이 주는 값을 this.props.number 로 받아서 표시만 함. 본인은 store의 존재도 모름)

  1. indxe.js 에서 React-redux 를 사용해서 App이 react-redux의 provider 의 지배를 받도록 만들어 주면, 1-2, 5-1에서 react-redux의 connect를 사용하여 store에 상태를 전달하거나 store에 이벤트를 전달하도록 할 수 있다. 나머지는 그냥 컴포넌트가 표시만 해 주는 역할을 수행한다(1-1, 2, 3, 4, 5-2).

/store.js

React-redux 사용전(redux 만 사용)과 동일.

import {createStore} from 'redux';

export default createStore(function(state, action){

    if(state === undefined){
        return {number:0};
    }
    if(action.type === 'INCREMENT'){
        // 01. state 를 복제하고, 복제한 state 에서 변경된 값만 갱신해서 return 한다.
        return {...state, number:state.number + action.size}
    }
    return state;
    },
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); // 디버깅용

/index.js

애플리케이션의 최상위 컴포넌트(index.js) 에다가 import {Provider} from 'react-redux'; 선언해서 로 아래와 같이 감싼다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from './store';

/*
01. 애플리케이션의 최상위 컴포넌트(index.js) 에다가 store 를 선언함.
import {Provider} from 'react-redux'; // 이 코드로 react-redux가 제공하는 컴포넌트를 선언함.
그리고 <App /> 를 <Provider></Provider>로 아래와 같이 감싼다. 
    <Provider>
    <App />
    </Provider>
그러면 App 이라는 우리의 최상위 컴포넌트는 Provider 라는 컴포넌트의 맥락 안에서 존재하기 때문에
Provider 의 지배를 받게 된다.

02. Provider는 store 를 받아야 하는데 store 는 이전예제에서 했던 store.js에 있는 store 임.
Provider 에 있는 store 라는 props 를 통해서 Redux store 를 공급해줌.
그러면 이 Provider 컴포넌트 하위에 있는 모든 컴포넌트들은 이 Redux store 에 접근할 수 있게 됨.
>>>> import 를 따로 시키지 않아도 store를 인식할 수 있게 됨 <<<< 
굉장히 편리해 짐. 더이상 "import store from './store';" 와 같이 import 시킬 필요가 없어짐.
    <Provider store={store}>
    <App />
    </Provider>
*/

// Strict Mode StrictMode 는 앱 내의 잠재적인 문제를 알아내기 위한 도구이다. 개발모드에만 영향을 끼친다.
// ReactDOM.render(
//   <React.StrictMode>
//     <App />
//   </React.StrictMode>,
//   document.getElementById('root')
// );

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);


// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1-1. /components/AddNumber.jsx

React-redux 사용전(redux 만 사용)과 동일.

import React, {Component} from 'react';

/*
export default class AddNumber extends Component {
  state = {size:1}
    render() {
      return (
        <div>
          <h1>Add Number</h1>
          <input type="button" value="+" onClick={function(){
            store.dispatch({type:'INCREMENT', size:this.state.size});
          }.bind(this)}></input>
          <input type="text" value={this.state.size} onChange={function(e){
            this.setState({size:Number(e.target.value)});
          }.bind(this)}></input>
        </div>
      )
    }
  }

  위와 같은 component 모양새는 Redux 의 store 에 의존하고 있기 때문에 AddNumber 를 
  다른 곳에서 사용하는 것이 어렵고 재활용성이 가능한 component 는 아닌것이 된다.
  이를 해결할 방법은 랩핑을 하는 것이다.
  AddNumber라는 component를 감싸는 새로운 component를 만든다
  그리고 그 component는 Redux의 store 를 핸들링 하는 component로 하게 한다.
  AddNumber라는 component는 Redux라는게 이 세상에 있다는 것을 모르는(Redux에 종속되지 않는) component로 만든다.

  이런 개념을 
  - presentational component : Redux라는게 이 세상에 있다는 것을 모르는(Redux에 종속되지 않는) component. 
  - container component 컴포넌트 : Redux, store 와 관련된 작업을 실질적으로 처리하는 component.

  Redux에 종속된다고 잘못된 것은 아니다.
  이 녀석이 다른곳에 사용될 가능성이 없다. 귀찮다. -> Redux에 종속되도록 코딩해도 상관없음.
*/
export default class AddNumber extends Component {
  state = {size:1} // AddNumber 내에서만 인식 가능한 데이터
    render() {
      return (
        <div>
          <h1>Add Number</h1>
          <input type="button" value="+" onClick={function(){
            this.props.onClick(this.state.size);
            // this.props.clickEvent(this.state.size); // props 로 상위 컴포넌트에 돌려줄 필드이름을 onClick 또는 clickEvent 같이 알아보기 좋도록 자유롭게 선언 가능하다.
          }.bind(this)}></input>
          <input type="text" value={this.state.size} onChange={function(e){
            this.setState({size:Number(e.target.value)});
          }.bind(this)}></input>
        </div>
      )
    }
  }

1-2. /containers/AddNumber.jsx

React-Redux 를 사용해서 export default connect(null, mapReduxDispatchToReactProps)(AddNumber); 와 같이 사용한다.

import AddNumber from "../components/AddNumber";
import {connect} from 'react-redux';

// 02. connect 의 두번째 인자로 선언하는 함수는 인자로 store.dispatch 함수를 가지고 있음.
function mapReduxDispatchToReactProps(dispatch){ // mapDispatchToProps
    return {
        clickEvent:function(size){
            dispatch({type:'INCREMENT', size:size});
        }
    }
}

// 01. AddNumber 는 store 에서 값을 가져와서 표시해야 할 필요는 없지만(첫번째 인자 함수에서 처리),
// store 변경후 알려줄 필요(Dispatch)가 있기 때문에 아래와 같이 첫번째 인자는 null, 두번쨰 인자는 mapReduxDispatchToReactProps() 로 선언한다.
// export default connect(store에 상태전달, store 에 이벤트 전달)(DisplayNumber);
// export default connect(store에서 값 취득해서 컴포넌트에 뿌릴 필요가 있을때 처리, store 값 변경후 store에 보고해야 할 필요가 있을때 처리)(DisplayNumber);
export default connect(null, mapReduxDispatchToReactProps)(AddNumber);

/*
import React, { Component } from "react";
import store from '../store';

export default class extends Component{
    render(){
        return <AddNumber clickEvent={function(size){
            store.dispatch({type:'INCREMENT', size:size});
        }.bind(this)}></AddNumber>
    }
 }
*/

2. /components/AddNumberRoot.jsx

React-redux 사용전(redux 만 사용)과 동일.

import React, {Component} from 'react';
import AddNumber from "../containers/AddNumber";

/*
1. Redux 가 없을 때 - props 로 onClick 이벤트 실행시켜서 보내주고 받고 쇼를 해야했음.
<AddNumber onClick={function(size){
              this.props.onClick(size);
          }.bind(this)}></AddNumber>
2. Redux 가 있을 때 

*/
export default class AddNumberRoot extends Component{
    render(){
      return (
        <div>
          <h1>Add Number Root</h1>
          <AddNumber></AddNumber>
        </div>
      );
    }
  }

3. /App.js (root)

React-redux 사용전(redux 만 사용)과 동일.

import React, {Component} from 'react';
import './App.css';
import AddNumberRoot from "./components/AddNumberRoot";
import DisplayNumberRoot from "./components/DisplayNumberRoot";

class App extends Component {
  state = {number:0}
  render(){
    return (
      <div className="App">
        <h1>Root</h1>
        <AddNumberRoot></AddNumberRoot>
        <DisplayNumberRoot></DisplayNumberRoot>
      </div>
    );
  }
}

export default App;

4. /components/DisplayNumberRoot.jsx

React-redux 사용전(redux 만 사용)과 동일.

import React, {Component} from "react";
import DisplayNumber from "../containers/DisplayNumber";

/*
1. Redux 가 없을 때
<DisplayNumber number={this.props.number}></DisplayNumber>

2. Redux 가 있을 때
<DisplayNumber></DisplayNumber>

*/

export default class DisplayNumberRoot extends Component{
    render(){
      return (
        <div>
          <h1>Display Number Root</h1>
          <DisplayNumber unit="kr"></DisplayNumber>
        </div>
      )
    }
  } 

5-1. /containers/DisplayNumber.jsx

React-Redux 를 사용해서 export default connect(mapReduxStateToReactProps)(DisplayNumber); 와 같이 사용한다.

import React, { Component } from "react";
import DisplayNumber from "../components/DisplayNumber";
import {connect} from 'react-redux';
// 05. react 도 state 를 가지고, redux 도 state 를 가진다.
// mapReduxStateToReactProps() 함수는 Redux 의 state 를 React 의 props 로 맵핑해 준다.
// Redux 에서 store 의 값이 변경될때 마다 mapReduxStateToReactProps() 함수가 호출되도록 약속되어 있음.

// 06. mapReduxStateToReactProps(state) 는 Redux 의 state 값을 인자로 받도록 약속되어 있기 때문에 
// function mapReduxStateToReactProps(state) 의 움직임은 아래 소스 부분과 똑같은 의미를 갖게 된다.
/*
state = {number:store.getState().number};

    store.subscribe(function(){
        this.setState({number:store.getState().number});
    }.bind(this));
 
    number={this.state.number}

이전에 위와 같이 조각조각 기입했던 내용이 아래 mapReduxStateToReactProps 함수로 다 축약이 된다.
*/
function mapReduxStateToReactProps(state){ // React Redux - mapStateToProps
    // Redux 의 state 값을 인자로 자동으로 받음.
    return {
        number: state.number
    }
}
// mapReduxDispatchToReactProps() 함수는 Redux 의 dispatch 를 react 의 props 로 연결해 준다.
function mapReduxDispatchToReactProps(){ // React Redux - mapDispatchToProps
    return {};
}
// 07. connect 의 첫번째 인자로 우리가 함수를 정의하고 그 함수는 redux 의 store 가 바뀔때 마다 호출되도록 약속되어 있고
// store 의 state 값이 인자로 전달되는데 개발자는 이 state 값을 가공해서 개발자가 return 하고 싶은
// 컴포넌트에 필요한 props 의 이름을 적고 (예) /components/DisplayNumber 의 경우 number 를 건내주어야 함)
// 그 props 에 공급될 값을 적어 주면 된다.

// 08. Dispatch 가 필요할 경우에는 두번째 인자에 dispatch 관련을 정의할 함수를 넣어준다.
// export default connect(mapReduxStateToReactProps, mapReduxDispatchToReactProps)(DisplayNumber);
// 09. Dispatch 가 필요 없는 경우에는 두번째 인자를 선언해 주지 않아도 된다.
// export default connect(mapReduxStateToReactProps)(DisplayNumber);

// DisplayNumber 는 store 에서 값을 가져와서 표시해야 할 필요는 있지만(첫번째 인자 함수에서 처리),
// store 변경후 알려줄 필요(Dispatch)는 없기 때문에 아래와 같이 첫번째 인자만 선언한다.
// export default connect(store에서 값 취득해서 컴포넌트에 뿌릴 필요가 있을때 처리, store 값 변경후 store에 보고해야 할 필요가 있을때 처리)(DisplayNumber);
export default connect(mapReduxStateToReactProps)(DisplayNumber);


// 02. 오리지널 컴포넌트인 /components/DisplayNumber.jsx 를 랩핑하는
// 새로운 컴포넌트를 export 했었는데 그것과 똑같음. 대신 connect 라는 메소드를 씀.

// 03. 아래와 같이 선언한 connect 함수는 괄호가 두번 나오니까
// export default connect()(); 
// 1) connect 를 실행하면 return 값이 함수이고
// 2) 그 return 된 함수를 다시 실행하는 것을 통해서 만들어진 값을 export 하는데 
// 3) 그 값이 이전 예제에서 수동으로 했었던 랩핑 컴퍼넌트를 만드는 것과 동일하게
// 랩핑된 컴퍼넌트가 return 되어짐.

// 04. index.js 에 provider 설정을 하고 나면 여기 있는 /components/DisplayNumber.jsx라는 컴포넌트를 
// connect 라는 함수의 인자로 전달하게 되면 (예) export default connect()(DisplayNumber);)
// connect 함수가 마법 뿅뿅 해서 이전에 작성한 BEFORE 와 같은 컴포넌트를 뱉어내게 되며 그래서 동작은 동일하게 이루어진다.


// 05. Why Use React Redux?
// https://react-redux.js.org/introduction/why-use-react-redux
// connect.js Explained 링크를 타고 가면 초창기 react-redux의 아주 초창기 모습을 간단한 코드로 표현한 것을 확인할 수 있다.
// gaearon/connect.js
// https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e

/*
// 01. React-Redux 도입하면 BEFORE와 같은 소스코드 선언 필요없음.
// AFTER 와 같이 소스 코드가 확 줄어듬.
// ===========================================================
// BEFORE
// - component 를 독립적으로 분리하기 위해 container component 로 수동 랩핑 해 주었음.
// - 각각의 컴포넌트마다 store 를 임포트 하였음.
// ===========================================================
import React, { Component } from "react";
import DisplayNumber from "../components/DisplayNumber";
import store from '../store';

// 이름없는 class 로 감싼다.
export default class extends Component{
    state = {number:store.getState().number};

    // store 에 데이터 값이 바뀔 때 마다 새로 화면 그려주기 위해 
    // 생성자에 subscribe(구독=state 변경시에 인지해서 화면 다시 그려주는 놈) 설정.
    // constructor(props){
    //     super(props);
    //     // // store 에 데이터 값이 바뀌었을 때 본인을 다시 render 해라는 뜻.
    //     // store.subscribe(function(){
    //     //     this.setState({number:store.getState().number});
    //     // }.bind(this));
    // }

    componentDidMount(){
        // store 에 데이터 값이 바뀌었을 때 본인을 다시 render 해라는 뜻.
        store.subscribe(function(){
            this.setState({number:store.getState().number});
        }.bind(this));
    }

    render(){
        return <DisplayNumber number={this.state.number} unit={this.props.unit}></DisplayNumber>
    }
 }
// ===========================================================
// AFTER
// - react-redux 로 component 를 수동 랩핑할 필요가 없어졌음.
// ===========================================================
import React, { Component } from "react";
import DisplayNumber from "../components/DisplayNumber";
import {connect} from 'react-redux';
export default connect()(DisplayNumber);
*/

5-2. /components/DisplayNumber.jsx

import React, {Component} from "react";

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

보충설명

yarn start
yarn build 

Pie's Tech Note

생계형 개발자의 메모장

comments powered by Disqus

    rss facebook twitter github youtube mail spotify instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora