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

相关推荐