Redux入門0x107: `react`整合`redux`精講
阿新 • • 發佈:2018-12-11
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') )