티스토리 뷰
생활코딩! React 리액트 프로그래밍 도서를 바탕으로 React 공부시작.
이번 진도는 308P ~ 358P.
목차로는 05. React Redux (챕터 05, 끝)
1. 개요
react는 사용자 정의 태그, 즉 컴포넌트를 만들어서 체계적이고 잘 정리된 어플리케이션을 만들 수 있게 한다.
redux는 컴포넌트에서 활용되는 상태(state)를 중앙에서 관리함으로써 데이터가 우리가 예측하지 않은 형태로 변할 가능성을 낮춰주는 기술이다.

일반적인 렌더링에서 한 컴포넌트가 사용중인 state 변경시, sibiling 컴포넌트에게 영향을 끼치려면 위로 올리고 올려서 다시 내려가는 절차를 거쳐야하지만, 해당 state를 store에서 중앙집중 관리할 경우 이와같은 걸치고 걸치는 단계가 필요없게된다. 이를 해결하기 위한 개념이 '구독'인데, 관여하고 싶은 state에 구독(subscribe)을 해놓음으로써 해당 state의 변경시 변경되었다고 알림을 받는 것이다.
2. without Redux
redux를 사용하지 않는다면, 하위 컴포넌트가 가지고 있는 값을 this.props의 이벤트를 통해서 위로 올려주고, 또 올려주고, 또 올려줘서 받은뒤에 다시 props로 내려줘야한다. 예제로 활용된코드의 경우엔, number라는 props를 App > Display Number Root > Display Number 와 같이 3계층을 내려갔으며, Display Number와 Display Number Root에서 각각 onClick 이벤트를 구현하여 다시 올려주곤했다.
3. with Redux
npm install redux
import {createStore} from "redux";
export default createStore(function(state, action){
if(state === undefined){
return {number:0}
}
if(action.type === 'INCREMENT'){
return {...state, number: state.number + action.size}
}
return state;
}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
number라는 state를 관리하는 store.js 파일이다.
createStore의 첫번째 인자로 reducer 함수를 전달받는다.
그리고 reducer 함수는 2개의 인자를 또 받으며 첫 번째는 store의 state, 두 번째는 store의 action이다.
state는 말그대로 상태값을 관리하는 객체이고, action은 해당 store에서 어떤 행동을 취할 것인지에 행위 정보가 들어있는 객체다. 또한, reducer 함수는 기본적으로 state를 리턴(return)해줘야한다.
createStore의 두번째 인자로 쓰인 값들(window~~~)은 크롬에서 확장 프로그램인 Redux DevTool을 사용하기 위해 지정한 값이다. 이를 지정하면 크롬 개발자도구의 Redux 탭에서 redux와 관련된 기능들을 활용할 수 있다.
store의 state를 변조하고자 하는 화면에선 아래와 같이 createStore를 정의한 store를 import 하고 dispatch 함수를 활용해주면 된다.
import store from './store.js'
// 중략
return (
<input type="button" value="Add" onClick={function(){
store.dispatch({type:'INCREMENT', size: this.state.size});
}.bind(this)}/>
)
store의 state의 변경에따라 변화가 필요한 화면의 경우엔 아래와 같이 subscribe를 정의한다.
import store from './store.js'
class Sample extends Component{
state = {number: store.getState().number};
constructor(props){
super(props);
store.subscribe(function(){
this.setState({number:store.getState().number});
}.bind(this));
}
render(){
return (
<div>
<h3>{this.state.number}</h3>
</div>
)
}
}
이러면 Sample 이라는 컴포넌트에서는 store의 state.number 값이 변경될 경우, subscribe 함수의 첫번째 인자로 전달한 함수가 실행되게 된다. 여기서는 그저 Sample의 state가 가진 number를 store의 number로 setState해주었다.
4. 래핑하기
앞선 [211024] React #06의 마지막 목차를 보면 컨테이너 컴포넌트와 프리젠테이션 컴포넌트가 나오는데, redux를 활용하는 곳에서도 그 개념이 나온다. 컨테이너 컴포넌트는 데이터를 가지고 관리하는 주체이기 때문에 리덕스 스토어와 관련된 작업을 실질적으로 처리하는 곳이며, 프리젠테이션 컴포넌트는 스토어와 관련하지않으며 그저 props를 통해 전달된 값을 화면에 잘 출력해서 보여주기만 하면 되는 컴포넌트다.
그리고 이를 위해 컨테이너 컴포넌트와 프리젠테이션 컴포넌트사이에,
redux 관련 처리를 담당하는 래핑 컴포넌트를 하나 만들어서 감싸준다.
5. react-redux
래핑 컴포넌트를 만들어줬을 때, 이슈가 되는 점은
컨테이너 컴포넌트에서 프리젠테이션 컴포넌트로 데이터를 전달할 게 추가될경우
중간에 존재하는 래핑 컴포넌트를 거쳐야하므로, 그에 대응하는 N개의 props등을 건내주는 로직을 추가로 정의함으로써 줄이고자했던 의존성이 증가하는 것이다.
그래서 이 점을 해결하려고 react-redux 를 활용하게 되는 것이다.
npm install react-redux
props가 여러개일 경우를 위해 자동화해주는 역할이라고 생각하면 되는데,
react-redux에서 핵심은 connect 함수다.
5-1. Provider
우선, react-redux를 통해 공급받는 기능을 추가해야하므로,
어플리케이션의 DOM 최상단에 Provider라는 컴포넌트를 추가해준다.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
위와같이 Provider라는 컴포넌트로 <App/>을 감싸고, props로 어떤 store를 공급받을 대상으로 할지도 정해준다. Provider는 UI와 연관된 컴포넌트가 아니기때문에 보여지는 룩앤필에는 이상이 없으며, props로 store를 정의함으로써, 이하 하위 컴포넌트들에서는 해당 store에 접근할 수 있는 창구를 열어주게 되는 것이다.
5-2. connect
그리고 이제 connect 함수를 활용하면 되는데...
connect는 아래와 같이 2개의 전달인자와 WrapperComponent를 필요로 한다.
import PresentaionalComponent from "../components/PresentaionalComponent";
import {connect} from "react-redux";
function mapStateToProps(state){
return {
number: state.number
}
}
function mapDispatchToProps(){
return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(PresentaionalComponent);
react-redux에서 제공하는 connect 함수를 뜯어보면...대략 아래와 같은 구조라서 위와같은 형태가 가능해진다.
function connect(mapStateToProps, mapDispatchToProps){
return function(WrappedComponent){
return class extends React.Component{
render(){
return(
<WrappedComponent
{...this.props}
{...mapStateToProps(store.getState(), this.props)}
{...mapDispatchToProps(store.dispatch, this.props)}
/>
)
}
componentDidMount(){
this.unsubscribe = store.subscribe(this.handleChange.bind(this))
}
componentWillUnmount(){
this.unsubscribe();
}
handleChange(){
this.forceUpdate();
}
}
}
}
즉, 내가 WrappedComponent가 프리젠테이션 컴포넌트의 역할을 하기 위해 정의됨과 동시에, 전달된 props를 구조분해 문법으로 전달해주는 것이다.
5-3. mapStateToProps
connect 함수의 첫번째 인자로, 리덕스의 state를 리액트의 props로 연결하는 역할.이다.
function mapStateToProps(state){
return {
number: state.number
}
}
이와 같이 어떤 객체를 반환하는데, 해당 객체가 프리젠테이션 컴포넌트의 props로 전달되는 것이다. 즉, 위와 같은 형태라면 number라는 이름을 가진 props에 state.number 값이 전달된다. 그리고 이 함수는 리덕스의 스토어 값이 변경될 때마다 호출되도록 약속(subscribe)되어있기 때문에, 전달하고자하는 props의 변경시에도 자동 호출되어 변경된 값을 전달하는 것이다.
그렇기때문에 mapStateToProps의 인자로 받는 state라는 값은, 리덕스 스토어의 state 객체이다.
5-4. mapDispatchToProps
connect 함수의 두번째 인자로, 프리젠테이션에서 발생한 이벤트를 잡아서 리덕스 스토어에 dispatch를 발생시켜야 할 경우 활용되는 인자다.
// AddNumber라는 프리젠테이션 컴포넌트
class AddNumber extends Component{
state = {size: 1}
render(){
return (
<div>
<h1>Add Number</h1>
<input type="button" value="+" onClick={function (e){
this.props.onClick(this.state.size); // 여기!!
}.bind(this)}/>
<input type="text" value={this.state.size} onChange={function(e){
this.setState({size: Number(e.target.value)})
}.bind(this)}/>
</div>
)
}
}
/**/
// AddNumber 컴포넌트를 감싼 래핑 컴포넌트
function mapDispatchToProps(dispatch){
return {
onClick: function(size){
dispatch({type:'INCREMENT', size: size});
}
}
}
export default connect(null, mapDispatchToProps)(AddNumber);
위는 2개 코드를 합친건데, AddNumber 라는 프리젠테이션 컴포넌트에서 "+" 버튼 클릭(onClick)시, this.props.onClick 함수를 호출하는데, 해당 함수가 바로 래핑 컴포넌트에 정의된 onClick: function(size){} 를 호출하며, 그 내부에서 dispatch를 사용함으로써 리덕스 스토어에 액션을 정의해주는 것이다.
그리고 이렇게 프리젠테이션 컴포넌트와 연결지어주는 부분이 바로, connect 함수의 두번째 인자로 전달을 해준 부분이다.
'개발 > 개발일지' 카테고리의 다른 글
Vue로 Slide Puzzle 만들기(feat. 개발자가 선배님 결혼식을 축하하는 법) (1) | 2021.11.19 |
---|---|
[211024] React #06 : ajax(fetch) (0) | 2021.10.24 |
[211023] React #05 : router (0) | 2021.10.24 |
[211022] React #04 : function & class (0) | 2021.10.23 |
[211020] React #03 : (props vs state) + CRUD (0) | 2021.10.20 |