0x000 概述
前面雖然簡單的講了如何在react
中集成redux
,但是那只是簡單的講講而已,這一章將會仔細講講如何在react
中使用reudx
0x001 問題分析
查看前邊的栗子:
import {createStore} from 'redux'
import React from 'react'
import ReactDom from 'react-dom'
//reducer
const counter = (state = 0, action) => {
switch (action.type) {
case ACTION_INCREMENT:
return state + 1
case ACTION_DECREMENT:
return state - 1
default:
return state
}
}
// action
const ACTION_INCREMENT = 'INCREMENT'
const ACTION_DECREMENT = 'DECREMENT'
// action creator
const increment = () => ({
type: ACTION_INCREMENT
})
const decrement = () => ({
type: ACTION_DECREMENT
})
// store
const store = createStore(counter)
// react
// // 組件
class App extends React.Component {
constructor() {
super()
// 初始化 state
this.state = {
counter: 0
}
// 監聽 store 變化, store 變化的時候更新 counter
this.unSub=store.subscribe(() => {
this.setState({
counter: store.getState()
})
})
}
// 組件銷燬的時候取消訂閱
componentWillUnmount(){
this.unSub()
}
render() {
return <div>
<p>{this.state.counter}</p>
<button
onClick={() => {
store.dispatch(increment())
}}>+
</button>
<button
onClick={() => {
store.dispatch(decrement())
}}>-
</button>
</div>
}
}
ReactDom.render(
<App/>,
document.getElementById('app')
)
爲了讓組件可以響應redux
的變化,我們寫了一些代碼:
....
// 監聽 store 變化, store 變化的時候更新 counter
this.unSub=store.subscribe(() => {
this.setState({
counter: store.getState()
})
})
....
// 組件銷燬的時候取消訂閱
componentWillUnmount(){
this.unSub()
}
如果我們有大量的組件需要綁定redux
,那麼寫這些代碼就顯得非常冗餘了
這一章要做的事就是優化掉這個東西
0x002 connect
方法
這裏用了一個react
的HOC
,參數是一個組件,返回值也是一個組件,但是返回的組件被添加了一個props
,也就是state
。connect
方法爲每個組件添加了響應store
數據變化的能力,在store.dispatch
調用的時候,會修改組件的props
,讓組件重繪,從而達到react
組件和redux
綁定但是又不需要寫太多樣板代碼
-
connect
const connect = (WrappedComponent) => { return class Control extends React.Component { constructor() { super() this.state = { state: 0 } this.unSub = store.subscribe(() => { this.setState({ state: store.getState() }) }) } componentWillUnmount() { this.unSub() } render() { return <WrappedComponent state={this.state.state}/> } } }
-
完整源碼
import {createStore} from 'redux' import React from 'react' import ReactDom from 'react-dom' //reducer const counter = (state = 0, action) => { switch (action.type) { case ACTION_INCREMENT: return state + 1 case ACTION_DECREMENT: return state - 1 default: return state } } // action const ACTION_INCREMENT = 'INCREMENT' const ACTION_DECREMENT = 'DECREMENT' // action creator const increment = () => ({ type: ACTION_INCREMENT }) const decrement = () => ({ type: ACTION_DECREMENT }) // store const store = createStore(counter) const connect = (WrappedComponent) => { return class Control extends React.Component { constructor() { super() this.state = { state: 0 } this.unSub = store.subscribe(() => { this.setState({ state: store.getState() }) }) } componentWillUnmount() { this.unSub() } render() { return <WrappedComponent state={this.state.state}/> } } } // 子組件 class SubCom extends React.Component { render(){ return <p>{this.props.state}</p> } } // 包裹這個組件 let ReduxSubCom=connect(SubCom) // react 組件 class App extends React.Component { constructor() { super() } render() { return <div> <ReduxSubCom/> <button onClick={() => { store.dispatch(increment()) }}>+ </button> <button onClick={() => { store.dispatch(decrement()) }}>- </button> </div> } } // 包裹組件 let ReduxApp = connect(App) ReactDom.render( <ReduxApp/>, document.getElementById('app') )
0x003 加強connect
方法,消除訂閱整個state
樹的影響
雖然已經實現了將state
和組件綁定,但是我們綁定的是整個state
,如果state
樹很大並且組件很多,那這個無畏的性能消耗太兇了。
- 修改
redux
結構
const counter = (state = {counter: 0, num: 0}, action) => {
switch (action.type) {
case ACTION_INCREMENT:
return {...state, ...{counter: ++state.counter}}
case ACTION_DECREMENT:
return {...state, ...{counter: --state.counter}}
default:
return state
}
}
- 修改
connect
方法,返回一個函數,並修改props
傳參:
const connect = (mapStateToProps) => {
return (WrappedComponent) => class Control extends React.Component {
constructor() {
super()
this.state = {
state: {}
}
this.unSub = store.subscribe(() => {
let state = mapStateToProps(store.getState())
this.setState({
state: state
})
})
}
componentWillUnmount() {
this.unSub()
}
render() {
return <WrappedComponent {...this.state.state}/>
}
}
}
- 修改
APP
組件中的props
訪問方式
class App extends React.Component {
constructor() {
super()
}
componentWillReceiveProps(nextProps) {
console.log(nextProps)
}
render() {
return <div>
<p>{this.props.counter}</p>
<button
onClick={() => {
store.dispatch(increment())
}}>+
</button>
<button
onClick={() => {
store.dispatch(decrement())
}}>-
</button>
</div>
}
}
- 修改
connect
調用
let ReduxApp = connect((state) => {
return {
counter: state.counter
}
})(App)
0x004: 加強connect
,讓代碼中不再調用store.dispatch
,不在依賴redux
-
修改
connect
方法,除了吧state
映射到props
上,也把dispatch
給映射上去了,這樣組件就感受不到redux
的存在了const connect = (mapStateToProps, mapDispatchToProps) => { return (WrappedComponent) => class Control extends React.Component { constructor() { super() // 第一次初始化 let props = mapStateToProps(store.getState()) let actions = mapDispatchToProps(store.dispatch) this.state = { props: {...props,...actions} } this.unSub = store.subscribe(() => { // 變化的時候再次計算 let props = mapStateToProps(store.getState()) let actions = mapDispatchToProps(store.dispatch) this.setState({ props: {...props,...actions} }) }) } componentWillUnmount() { this.unSub() } render() { return <WrappedComponent {...this.state.props}/> } } }
-
修改
connect
調用,將dispatch
映射到組件上let ReduxApp = connect( (state) => { return { counter: state.counter } }, (dispatch) => { return { increment: () => dispatch(increment()), decrement: () => dispatch(decrement()), } } )(App)
-
修改
App
組件,不再使用store.dispatch
,而是使用connect
傳遞過來的dispatch
,讓組件不依賴redux
class App extends React.Component { constructor(props) { super() } render() { const {counter,increment,decrement}=this.props return <div> <p>{counter}</p> <button onClick={increment}>+ </button> <button onClick={decrement}>- </button> </div> } }
-
完整源碼
import {createStore} from 'redux' import React from 'react' import ReactDom from 'react-dom' //reducer const counter = (state = {counter: 0, num: 0}, action) => { switch (action.type) { case ACTION_INCREMENT: return {...state, ...{counter: ++state.counter}} case ACTION_DECREMENT: return {...state, ...{counter: --state.counter}} default: return state } } // action const ACTION_INCREMENT = 'INCREMENT' const ACTION_DECREMENT = 'DECREMENT' // action creator const increment = () => ({ type: ACTION_INCREMENT }) const decrement = () => ({ type: ACTION_DECREMENT }) // store const store = createStore(counter) const connect = (mapStateToProps, mapDispatchToProps) => { return (WrappedComponent) => class Control extends React.Component { constructor() { super() // 第一次初始化 let props = mapStateToProps(store.getState()) let actions = mapDispatchToProps(store.dispatch) this.state = { props: {...props,...actions} } this.unSub = store.subscribe(() => { // 變化的時候再次計算 let props = mapStateToProps(store.getState()) let actions = mapDispatchToProps(store.dispatch) this.setState({ props: {...props,...actions} }) }) } componentWillUnmount() { this.unSub() } render() { return <WrappedComponent {...this.state.props}/> } } } // react 組件 class App extends React.Component { constructor(props) { super() } render() { const {counter,increment,decrement}=this.props return <div> <p>{counter}</p> <button onClick={increment}>+ </button> <button onClick={decrement}>- </button> </div> } } let ReduxApp = connect( (state) => { return { counter: state.counter } }, (dispatch) => { return { increment: () => dispatch(increment()), decrement: () => dispatch(decrement()), } } )(App) ReactDom.render( <ReduxApp/>, document.getElementById('app') )
0x004 reat-redux
以上效果就和上一章的效果一致,是一個counter
,在這裏我們一步一步去除了樣板代碼,並將redux
從組件中移除,如果只看App
組件,根本感覺不到redux
的存在,但redux
又確實存在,如果有一天你要去掉redux
,就可以做到不影響組件了。這裏的connect
其實不需要自己寫,已經有好的實現了:react-redux
// 引入`react-redux`
import {Provider, connect} from 'react-redux'
// 修改組件
ReactDom.render(
<Provider store={store}>
<ReduxApp/>
</Provider>,
document.getElementById('app')
)
-
完整源碼
import {createStore} from 'redux' import React from 'react' import ReactDom from 'react-dom' import {Provider, connect} from 'react-redux' //reducer const counter = (state = {counter: 0, num: 0}, action) => { switch (action.type) { case ACTION_INCREMENT: return {...state, ...{counter: ++state.counter}} case ACTION_DECREMENT: return {...state, ...{counter: --state.counter}} default: return state } } // action const ACTION_INCREMENT = 'INCREMENT' const ACTION_DECREMENT = 'DECREMENT' // action creator const increment = () => ({ type: ACTION_INCREMENT }) const decrement = () => ({ type: ACTION_DECREMENT }) // store const store = createStore(counter) // react 組件 class App extends React.Component { constructor(props) { super() } render() { const {counter, increment, decrement} = this.props return <div> <p>{counter}</p> <button onClick={increment}>+ </button> <button onClick={decrement}>- </button> </div> } } let ReduxApp = connect( (state) => { return { counter: state.counter } }, (dispatch) => { return { increment: () => dispatch(increment()), decrement: () => dispatch(decrement()), } } )(App) ReactDom.render( <Provider store={store}> <ReduxApp/> </Provider>, document.getElementById('app') )