React Redux 기본학습 -3
생활코딩의 React Redux 강좌를 보면서 학습한 내용 정리용.
Redux 종속성 제거
실습 화면은 Redux를 사용하지 않았던 것과 동일.
1-1. components/AddNumber.jsx(this.props.onClick 이벤트로 입력받은 number 를 return 함.
1-2. containers/addNumber.jsx(Container component) (1-1의 AddNumber.jsx에서 onClick 이벤트가 발생하면 값을 넘겨받아서 store 에 담는 역할을 함)
2. components/AddNumberRoot.jsx(아무것도 안해도 됨)
3. Root(/app.js) (아무것도 안해도 됨)
4. components/DisplayNumberRoot.jsx (아무것도 안함)
5-1. containers/DisplayNumber.jsx (store 의 데이터가 바뀌었을때 새로 렌더링 하라는걸 통지하는 subscribe 를 여기에서 설정함. 그리고 5-2 가 필요한 값인 1-1에서 입력받았을 number 를 props로 건내줌)
5-2. components/DisplayNumber.jsx (store의 값이 바뀌든 말든 얘는 5-1.이 필요하면 새로 렌더링하고, 5-1이 주는 값을 this.props.number 로 받아서 표시만 함. 본인은 store의 존재도 모름)
1-2, 5-1만 store를 의식해서 처리함. 나머지는 그냥 컴포넌트만 짜서 보여주거나(2,3,4) 이벤트만 발생시켜서 상위 컴포넌트에 보내주거나(1-1) 상위 컴포넌트에서 받은 값을 뿌리기만 함(5-2).
/store.js
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__()); // 디버깅용
1-1. /components/AddNumber.jsx
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
/*
/components/AddNumber.jsx 를 랩핑할 /containers/AddNumber.jsx 를 만든다.
왜 만드냐? /components/AddNumber.jsx 의 Redux 종속성을 없애기 위해.
이 container component는 1대 1 관계가 되어야 하는건 아니고 이 component 하나가
여러개의 react component 들을 감싸는 역할을 해도 되고 이 container component는
redux store 만 상대를 하는게 아니라 여러가지 비지니스 로직을 처리할 수 도 있다.
/containers/AddNumber.jsx 는 /components/AddNumber.jsx 를 그대로 가져와서
뿌려주는 역할을 한다.
어플과 종속되는 작업은 이놈이 처리하고 기존의 /components/AddNumber.jsx 는
화면에 무언가를 표시하는 것에 집중시킴.
*/
import React, { Component } from "react";
import AddNumber from "../components/AddNumber";
import store from '../store';
export default class extends Component{
render(){
return <AddNumber onClick={function(size){
store.dispatch({type:'INCREMENT', size:size});
}.bind(this)}></AddNumber>
}
// AddNumber에서 onClick으로 값이 넘어오면 onClick 으로 받고, clickEvent 으로 넘어 오면 clickEvent으로 받는등 이름은 자유롭게 붙이는게 가능하다.
// render(){
// return <AddNumber clickEvent={function(size){
// store.dispatch({type:'INCREMENT', size:size});
// }.bind(this)}></AddNumber>
// }
}
2. /components/AddNumberRoot.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>
);
}
}
3. /App.js (root)
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
import React, {Component} from "react";
import DisplayNumber from "../containers/DisplayNumber";
export default class DisplayNumberRoot extends Component{
render(){
return (
<div>
<h1>Display Number Root</h1>
<DisplayNumber></DisplayNumber>
</div>
)
}
}
5-1. /containers/DisplayNumber.jsx
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}></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>
)
}
}