1. 程式人生 > >Redux入門0x107: `react`整合`redux`精講

Redux入門0x107: `react`整合`redux`精講

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方法

這裡用了一個reactHOC,引數是一個元件,返回值也是一個元件,但是返回的元件被添加了一個props,也就是stateconnect方法為每個元件添加了響應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')
    )

0x005 資源