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 資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章