예제는 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 의 각각의 사용 목적과 개념은 다음과 같다.
이렇게 프레젠테이션 컴포넌트는 화면에 보여주기만을
프레젠테이션 컴포넌트의 기능을 따로 빼서 컨테이너 컴포넌트에 만들어서 분리를 시켜 재사용성을 하게끔 만들어줍니다.
이제 컨테이너 컴포넌트를 만들어보겠습니다.
src 폴더 안에 containers 폴더를 만들고 AddNumber.jsx, DisplayNumber.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를 보겠습니다.
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에서 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도 변경해주도록하겠습니다.
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> ); } }
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;
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함수는 컨테이너 컴포넌트를 만드는 또 다른 방법이며,
클래스형 컴포넌트로 작성을 하게 되는 경우에는 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를 보겠습니다.
기존에 있던 소스와 비교해보면 좀더 쉽게 이해할 수 있습니다.
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 컨테이너 컴포넌트도 변경해보도록 하겠습니다.
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 컴포넌트를 사용한 예제입니다.