Redux 도입
작성 수정
1. Redux를 쓰는 이유
- 프로젝트 규모가 커지면, 컴포넌트 간
state,props전달이 복잡해진다. Redux를 도입하면State를 중앙에서 효율적으로 관리할 수 있다.Redux는React뿐만아니라,Angular,Vue.js,Vanilla JS등 JS 언어내의 여러곳에서 사용한다.
2. Redux 사용하기
- 스토어 객체 생성
- 스토어 메서드 사용
- 스토어 구독
1) 스토어 (store) 객체 생성하기
// store.js
import {createStore} from 'redux'
function reducer(state, action) {
if(state === undefined){
return {number : 0}
}
if(action.type === "INCREMENT"){
return {...state, number:state.number + action.size}
}
return state
}
const devTool = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
export default createStore(reducer, devtool)
코드설명
createStore(reducer, devtool):store객체 반환reducer(state, action):state반환state:redux가 관리하는stateaction:state를 처리하는 방법
2. store 메서드 이용하기
1) store.dispatch(action): action을 통한 state 수정
import store from "./store"
class AddNumber extends Component {
state = {size:1}
render(){
return(<input onClick={() => {
store.dispatch({type:"INCREMENT", size:this.state.size})
}>)
}
}
2) store.getState(): state 값 얻기
import store from "./store"
const DisplayNumber =() => {
const[number, setNumber] = useState(store.getState().number)
store.subscribe(()=> setNumber(store.getState().number))
return(<div>
{number}
</div>)
}
3) store.subscribe(): store 구독하기
store.subscribe(callback): 스토어의 값이 변경됐을 때callback호출- 스토어의
state가 바뀐 사실을 알 수 있게 구독해야 한다.
constructor(props){
super(props);
store.subscribe(function(){
this.setState({number:store.getState().number})
}.bind(this))
}
3. 리덕스 종속성 제거
- 컴포넌트의 독립성 보장하여 재사용성을 높이기 위해
컴포넌트 나누기
- 컨테이너 컴포넌트
- 리덕스 스토어 연관 작업 처리
- 프레젠테이셔널 컴포넌트
props로 전달받은 데이터를 출력, 함수를 호출하는 일 담당
- 컨테이너 디렉토리 따로 생성, 파일명은 동일하게 생성
Examples
Before) AddNumber
import store from "./store
class AddNumber extends Component {
state = {size:1}
render() {
return (
<div>
<input onClick={()=> store.dispatch({...})}/>
</div>
)
}
}
After) ./containers/AddNumber
import AddNumber from "./components/AddNumber"
import store from "./store"
export default class extends Component {
render(){
return <AddNumber onClick={(size)=> store.dispatch({...})}></AddNumber>
}
}
After ./components/AddNumber
class AddNumber extends Component {
state = {size:1}
render() {
return (
<div>
<input onClick={()=> this.props.onClick(size)}/>
</div>
)
}
}
- 리덕스와 관련된 작업은
AddNumber를 래핑하는컨테이너 컴포넌트가 수행한다. - 이를 통해,
AddNumber컴포넌트는 리덕스의 기능이 필요하지 않은 곳에서도 재사용할 수 있다.
3. react-redux 사용하기
1) 도입 배경
- 상위에서
props를 전달할 때 중간에 익명 컨테이너가 끼었기 때문에, 컨테이너에서도props을 전달해야하는 비효율 발생 DisplayNumber컨테이너 상위 ➡DisplayNumber 컨테이너➡DisplayNumber 컴포넌트
import DisplayNumber from './containers/DisplayNumber'
<DisplayNumber unit="kg" isActive={true}/>
import DisplayNumber from './components/DisplayNumber'
<DisplayNumber unit="kg" isActive={true}/>
// 컨테이너 컴포넌트도 굳이 props를 전달하는 코드를 작성해야함😥
react-redux를 사용하면 익명 래퍼 컨테이너가props를 전달하는 번거로움과 리덕스 관련 중복 코드 해결할 수 있다.
1. Provider로 App 감싸기
import {Provider} from "react-redux"
<Provider store = {store}>
<App />
</Provider>
Provider를 통해 스토어를 공급받아, 하위 컴포넌트에서 따로store에 접근할 필요가 없어진다.
2. connect()() 사용하기
connect())(wrappedComponent): 컨테이너 컴포넌트 반환connect함수 호출 후, 래핑할 컴포넌트를 인자로 넘겨 다시 호출- 반환된 컨테이너 컴포넌트는
wrappedComponent를 반환한다.
// containers/DisplayNumber
import DisplayNumber from "./components/DisplayUser"
import {connect} from "react-redux"
export default connect()(DisplayNumber);
connect함수를 통해props을 전달하는 코드를 중복해서 작성하지 않고, 자동으로 넘겨줄 수 있다.
3. connect 함수에 리덕스 관련 코드 넣기
connect(mapStateToProps, mapDispatchToProps)(wrappedComponent)connect의 첫번째 함수에 들어가는 인자들은,wrappedComponent에props이 된다.
1) mapStateToProps 사용
mapStateToProps(state, props)- 리덕스의
state를 리액트의props로 연결하는 역할을 한다. - option: 두번째 인자
props를 이용할 수도 있다.
import DisplayNumber from '../components/DisplayNumber';
import { connect } from 'react-redux';
const mapReduxStateToReactProps = (state) => {
return { number: state.number };
};
const mapReduxDispatchToReactProps = () => {
return {};
};
export default connect(
mapReduxStateToReactProps,
mapReduxDispatchToReactProps
)(DisplayNumber);
const DisplayNumber = ({ number }) => {
return (
<>
<h1>Display Number</h1>
<input type='text' value={number} readOnly />
<h5>{test}</h5>
</div>
);
};
2) mapDispatchToProps 사용
mapDispatchToProps(dispatch)- 리덕스의
dispatch를 리액트 컴포넌트의props로 연결하는 함수다.
import { connect } from 'react-redux';
import AddNumber from '../components/AddNumber';
const mapDispatchToProps = (dispatch) => {
return {
onClick: (size) => {
dispatch({ type: 'INCREMENT', size: size });
},
};
};
export default connect(null, mapDispatchToProps)(AddNumber);
import { useState } from 'react';
const AddNumber = ({ onClick }) => {
const [size, setSize] = useState(1);
return (
<div>
<h1>Add Number</h1>
<input type='text' readOnly value={size} />
<input type='button' value='+' onClick={() => onClick(size)} />
</div>
);
};
4. connect 함수 내부 동작 살펴보기
function connect(mapStateToProps, mapDispatchToProps){
return function(WrappedComponent){
return class extends React.Component{
render(){
return(<WrappedComponent // 1️⃣
{...this.props}
// ✅ connect 함수 반환 값인 컴포넌트에 props을 전달했을 때
// ✅ wrappedComponent에 props을 주입하는 코드
{...mapStateToProps(store.getState(), this.props)}
{...mapDispatchToProps(store.dispatch, this.props)}
/>)
}
componentDidMount(){
store.subscribe(this.handleChange.bind(this))
}
componentWillUnmount() {
this.unsubscribe()
}
handleChange() {
this.forceUpdate()
// 리덕스 스토어 값이 변경되면 강제로 render호출
}
}
}
}
- 1️⃣
WrappedComponent에 전달되는props는 다음과 같다.- 컨테이너 컴포넌트 상위에서 주입된
props redux의stateredux의 이벤트 (dispatch)
- 컨테이너 컴포넌트 상위에서 주입된
connect 함수의 장점
- 등록한
props에 한해서만 구독하기 때문에, 불필요한render함수 호출이 줄어듦 shouldComponentUpdate를 통해 수동으로 해야할 일을 자동으로 처리해줌.