# 手摸手实现react-redux

# 为什么要使用react-redux

我们先来看下没有使用 react-redux 之前我们是如何使用redux




 
















































import React, {Component} from 'react'
import { bindActionCreators } from 'redux'
import * as types from '../store/action-types'
import store from '../store'
function increment(payload) {
    return {
        type: types.INCREMENT,
        payload,
    }
}

function decrement(payload) {
    return {
        type: types.DECREMENT,
        payload,
    }
}

let action = bindActionCreators({
    increment,
    decrement
}, store.dispatch)

export default class Counter1 extends Component{
    constructor(props) {
        super(props)
        this.state = {
            number: store.getState().number
        }
    }
    componentDidMount() {
        this.unSubscribe = store.subscribe(() => {
            this.setState({
                number: store.getState().number
            })
        })
    }
    componentWillUnmount() {
        this.unSubscribe()
    }
    render() {
        return (
            <div>
                <p>{ this.state.number }</p>
                <button onClick={action.increment}>+</button>
                <button onClick={action.decrement}>-</button>
            </div>
        )
    }
}

以上代码是应用redux实现一个简单的加减计数器,我们发现如果我们在项目中使用redux的话,每个组件都要重复以下逻辑

  • 引入仓库store

  • 利用actionCreator 创建 action

  • 利用 bindActionCreators 优化diapatch的代码

  • 在组件内部动态绑定state

  • 在组件的生命周期里面进行订阅和发布订阅

  • 在组件事件中进行action派发等

假设我们的项目有100个组件,那就意味着以上的逻辑我们要复制粘贴100次,而且业务多起来之后代码量多很难维护,这也是为什么我们需要使用react-redux的原因了。react-redux的作用,就是将我们的组件和仓库连接在一起,简化一些重复的操作

# react-redux的基本用法

我们试下将上述的例子用react-redux 实现一下




 








// index.js

import React from 'react'
import ReactDOM from 'react-dom'
import Counter1 from './components/Counter1'
import {Provider} from 'react-redux' // 引入Provider组件作为父容器
import store from './store'

ReactDOM.render(<Provider store={store}>
    <Counter1 />
    </Provider>, window.root)



 

































// counter.js

import React, {Component} from 'react'
import action from '../store/actions/conuter1'
import { connect } from 'react-redux'
import * as types from '../store/action-types'

class Counter1 extends Component{
    // .. 这里省略了绑定state,订阅取消订阅等操作
    // store的值和dispatch通过this.props获取
    render() {
        return (
            <div>
                <p>{ this.props.number }</p>
                <button onClick={this.props.increment}>+</button>
                <button onClick={this.props.decrement}>-</button>
            </div>
        )
    }
}
let mapStateToProps = (state) => state
let mapDispatchToProps = (dispatch) => (
    {
        increment() {
            dispatch({type: types.INCREMENT1})
        },
        decrement() {
            dispatch({type: types.DECREMENT1})
        }
    }
)
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter1)

是不是简洁了很多,下面我们主要挑几个难懂的点来介绍一下,然后实现一个简单的react-redux

# mapStateToProps 和 mapDispatchToProps

看了上面代码,我们可能大致知道了 mapStateToPropsmapDispatchToProps的作用是告诉 connect 组件需要的 state 内容和将要派发的动作,也就是负责一种映射的关系。

我们从代码 connect(mapStateToProps, mapDispatchToProps)(Counter1) 中可以知道,connect函数将storedispatch映射给Counter1组件,所以在组件中我们可以通过this.props.xxx来获取到store的值和dispatch的值,这个时候我们大致可以猜想一下是这样实现的<Counter1 {...store} {...dispatch} />,事实上确实是这样的。

但是细心的同学会发现,connect内部是怎么拿到storedispatch的,我们只需要看下mapStateToPropsmapDispatchToProps的结果答复呼之欲出了

那我们接下来总结一下react-redux的作用吧

  • 通过Provider组件将store往下传递
  • 通过connentstoredispatch映射给每个组件,在组件中可以通过this.props.xxx来使用

# 实现Provider




 









import React from 'react' 
import Context from './context'

export default class Provider extends React.Component {
    render() {
       return (
            <Context.Provider value={{ store: this.props.store }}>
                {this.props.children}
            </Context.Provider>
        )
    }
}

我们发现其实Provider组件很简单,其实就是调用上下文的api, 将store的值传递给下层组件,而且Provider组件自身不进行额外的渲染,我们只需要无条件的渲染子组件就完事了

# 实现connect - v1

上文中我们提到,react-redux的作用是将组件和仓库连接在一起,简化了一些如订阅、绑定state等操作,也提到了mapStateToPropsmapDispatchToProps的作用等,其实啊这些简化了的操作是在connect函数中进行了的,相当于在上层进行了封装,高阶函数的用法.

实现 connect




 


























import React from 'react'
import Context from '../components/context'
export default function(mapStateToProps, mapDispatchToProps) {
    return function(Component) {
        return class extends React.Component{
            static contextType = Context
            constructor(props, context) {
                super(props)
                this.state = mapStateToProps(context.store.getState()) // 拿到Provide组件通过上下文传递的store
            }
            componentDidMount() { // 进行订阅操作
                this.unsubscribe = this.context.store.subscribe(() => {
                    this.setState(mapStateToProps(this.context.store.getState()))
                })
            }
            componentWillUnmount() { // 进行取消订阅操作
                this.unsubscribe()
            }
            render() {
               let actions = mapDispatchToProps(this.context.store.dispatch)// 获取dispatch
               return (
                   // 将store和dispatch传递给目标组件,实现组件内通过this.props.xxx来调用
                   <Component {...this.props}  {...this.state} {...actions} /> 
               )
            }

        }
    }
}

# 实现connect - v2

但是这个还不是我们在日常项目中的最终用法,我们在日常项目中是这样使用的




 















// action/counter1.js

import * as types from '../action-types'
function increment(payload) { // actionCreator 简化action的创建的
    return {
        type: types.INCREMENT1,
        payload,
    }
}

function decrement(payload) {
    return {
        type: types.DECREMENT1,
        payload,
    }
}

export default {increment, decrement} //导出所有actionCreator



 















// Counter1.js

import action from '../store/actions/conuter1'
let mapStateToProps = (state) => state
// let mapDispatchToProps = (dispatch) => (
//     {
//         increment() {
//             dispatch({type: types.INCREMENT1})
//         },
//         decrement() {
//             dispatch({type: types.DECREMENT1})
//         }
//     }
// )
export default connect(
    mapStateToProps,
    action
)(Counter1)

我们发现我们省略了mapDispatchToProps的写法,因为还是比较冗余业务多起来,我们只需要传入这个组件的actionCreator就好了,那我们看下connect函数是如何处理这种写法的吧




 






























import React from 'react'
import Context from '../components/context'
import {bindActionCreators} from 'redux' // 引入bindActionCreators
export default function(mapStateToProps, mapDispatchToProps) {
    return function(Component) {
        return class extends React.Component{
            static contextType = Context
            constructor(props, context) {
                super(props)
                this.state = mapStateToProps(context.store.getState())
                if (typeof mapDispatchToProps === 'function') { //mapDispatchToProps 写法
                    this.actions = mapDispatchToProps(context.store.dispatch)
                } else if (typeof mapDispatchToProps === 'object') { // actions写法 是一个对象
                    this.actions = bindActionCreators(mapDispatchToProps, context.store.dispatch)
                }
            }
            componentDidMount() {
                this.unsubscribe = this.context.store.subscribe(() => {
                    this.setState(mapStateToProps(this.context.store.getState()))
                })
            }
            componentWillUnmount() {
                this.unsubscribe()
            }
            render() {
               return (
                   <Component {...this.props}  {...this.state} {...this.actions} />
               )
            }

        }
    }
}

我们可以利用reduxbindActionCreators来将传入的actions转化为我们所需要actions的就好了

# 总结

redux、redux中间件原理、react-redux花了我大概1个多月的时间才把他们消化完......可能是因为现在工作负责的项目的技术栈没有react吧,最近写的少了对redux的用法生疏了,不管怎么说,继续加油。

接下来让我们来见识一下react-hooks的神奇之处吧