redux
를 사용하는 이유는 뭘까?redux
를 사용하는 이유는 컴포넌트 간에state
값이 이동해야 하는 경우에, depth가 깊어지는 것을 방지하고state
값을 전역에서 관리할 수 있기 때문이다.
생활코딩의 react-redux 강의를 보고 개념 공부를 해서 위는 생활코딩의 예제다.
Add Number Root
안의 Add Number
의 button
을 누르면 Display Number Root
안의 Display Number
의 input값이 변경되어야 한다.
아직까지는, depth
가 그렇게까지 깊지는 않지만 위의 상황이 수십 수백번 반복된다고 생각하면 redux
의 필요성을 충분히 느끼게 될 것이다.
npm install --save react-redux
npm install --save-dev redux-devtools
이제 redux
를 사용하기 위해 install 해준다.
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
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;
}, composeWithDevTools());
store.js 파일을 src
아래에 만들고, 위와같이 createStore
를 해준다. createStore
의 인자로 들어가는 함수는 reducer
라고 불리는데, 간단하게 state
의 값을 관리해주는 곳이라고 생각하면 된다. reducer
의 state
와 component
의 state
는 조금은 다른 개념인데, 처음에 redux
의 state
가 값이 없기 때문에 초기값을 지정해주거나, 위와같이 값이 undefined
인 경우에 조건문을 걸어줘야 한다. 그 뒤로는 전달받은 action의 type
에 따라 조건을 걸어주고,Spread Operator
로 기존의 state
값을 복사해주고 바뀌는 값만 변경해준다. 지정되지 않은 action.type
이 들어올 때는 바로 state
를 return
해주면 된다.
composeWithDevTools
는 필수 기능은 아니지만, 아래와 같이 웹브라우저에서 확장프로그램 사용을 위해 필요하다.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
import "./index.css";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
원래는 redux
를 사용해야 하는 모든 파일에서 store
를 import
해야 하지만, 위처럼 Provider
를 사용하여 전체를 감싸주면 최상위 컴포넌트에서 store
를 한번만 import
해주면 된다.
import AddNumber from "../components/AddNumber";
import { connect } from "react-redux";
function mapDispatchToProps(dispatch) {
return {
onClick: (size) => {
dispatch({ type: "INCREMENT", size: size });
},
};
}
export default connect(null, mapDispatchToProps)(AddNumber);
AddNumber.js 파일에는 값을 변경시키는 버튼
이 있는데, 버튼
을 작동시켜서 store
의 변경된 값을 반영시키기 위해서는 원래 해당 함수
를 subscribe
해줘야 한다. 그런데 react
에서는 connect
를 사용하여 그런 작업을 할 필요 없게 해준다. 그리고 컴포넌트를 재활용하기 위해서 아래 AddNumber
와 같이 두개의 파일로 분리해서 container
에서는 값을 component
쪽으로 전달해주기만 하는데, connect
를 사용하면 위와 같은 분리 작업을 편리하게 만들어 준다.
AddNumber.js (container)
import AddNumber from "../components/AddNumber";
import { connect } from "react-redux";
function mapDispatchToProps(dispatch) {
return {
onClick: (size) => {
dispatch({ type: "INCREMENT", size: size });
},
};
}
export default connect(null, mapDispatchToProps)(AddNumber);
connect
의 첫번째 인자로는 변하게 할 state
의 값이 들어가고, 두번째로는 변하게하는 함수가 들어간다.
mapDispatchToProps
는 redux
의 dispatch
를 react
의 컴포넌트의 props
로 연결시키는 정보를 담고 있는 함수다.
AddNumber.js (component)
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={() => {
this.props.onClick(this.state.size);
}}
></input>
<input
type="text"
value={this.state.size}
onChange={(e) => {
this.setState({ size: Number(e.target.value) });
}}
></input>
</div>
);
}
}
Updated Information
위와 같이 파일을 2개로 만들 필요없이 하나의 파일에서connect
를 사용해도 된다. 하나의 파일에서도props
를 사용하는데 그 때 사용되는props
는store
에서 받아온state
를 지칭한다.(connect를 사용하면 component를 감싸고 있는 wrapper가 있다고 생각하면 된다)
DisplayNumber (Container)
import DisplayNumber from "../components/DisplayNumber";
import { connect } from "react-redux";
function mapReduxStateToReactProps(state) {
return {
number: state.number,
};
}
export default connect(mapReduxStateToReactProps)(DisplayNumber);
DisplayNumberRoot
는 state
의 값을 직접 변경하지 않기 때문에 두번째 인자의 값은 넣지 않는다.
mapReduxStateToReactProps
는 redux
의 store
의 state
를 react
의 props
를 mapping
시켜주는 정보를 담고 있는 함수다.
DisplayNumber (Component)
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> {this.props.unit}
</div>
);
}
}