手写React-Redux核心功能

518 阅读3分钟

简介

react-redux提供的api主要有两个:Provider和connect,Provider接受store参数,将其作为context;connect简单来说就是一个返回HOC的高阶函数——接受mapStateToProps和mapDispatchToProps,返回一个HOC,这个HOC将接受的组件装饰后返回。

Context

创建一个context并导出,Provider和connect需要引入这个context

// lib/react-redux/Context.js
import React from 'react';

const Context = React.createContext({});

export default Context

Provider

引入上面的Context,将接受的store参数作为Context.Provider的value,渲染内容是Provider的children

// lib/react-redux/Provider.js
import React from 'react';
import Context from './context';

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

connect

connect简单形式如下,现在返回的组件和WrappedComp没有什么差异,我们需要将Redux中的state和dispatch传给WrappedComp,让他可以读写state。

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComp) => {
    return class extends React.Component {
     	 render() {
              return <WrappedComp {...this.props} />
       }
    }
} 

先看看是如何使用connect,下面是VisibleTodoList.js的代码。

  • mapStateToProps,顾名思义,就是将state映射为props,参数是state和ownProps,返回值是一个对象,这里是将满足state.visibilityFilter这个过滤条件的todos返回
  • mapDispatchToProps,自然是将dispatch映射为props,实际上在组件中我们并不会调用dispatch,react-redux将dispatch封装了为一个函数,调用这个函数就相当于dispatch了某个action,比如,modifyTodo是一个actionCreator,返回一个type为'ModifyTodo'的action,执行onModifyTodo就是执行了dispatch(action)。mapDispatchToProps可以是个函数,参数是dispatch和ownProps, 返回值是对象;也可以是个对象。两种方式写法不同,react-redux内部应该将对象的写法转化为函数的写法,见下面转换
  • connect,接受mapStateToProps和mapDispatchToProps,返回一个函数HOC,HOC又接受一个组件,返回一个装饰过的组件
// containers/VisibleTodoList.js 部分代码
const mapStateToProps = state => {
  return {
    // 根据完成状态,筛选数据
    todos: getVisibleTodos(state.todos, state.visibilityFilter),
  }
}

// 函数形式
// const mapDispatchToProps = dispatch => {
//   return {
//     onDeleteTodo: id => {
//       dispatch(deleteTodo(id))
//     },
//     onModifyTodo: (id, text) => {
//       dispatch(modifyTodo(id, text))
//     },
//     onToggleTodo: id => {
//       dispatch(toggleTodo(id))
//     },
//   }
// }

// 对象形式
const mapDispatchToProps = {
  onDeleteTodo: (id) => deleteTodo(id),
  onModifyTodo: (id, text) => modifyTodo(id, text),
  onToggleTodo: id => toggleTodo(id),
}

// 观察上面两种mapDispatchToProps的写法可以猜测,将对象形式的写法转为函数形式的写法,可以将对象的key作为key,将key对应的值fn作为dispatch参数,再包装一层,
// 即函数形式返回对象的key: (...args) => {dispatch(fn(...args))},这个包装类似bindActionCreator

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

下面是connect的简单实现,只考虑了mapDispatchToProps作为函数和对象的不同处理方式, mergeProps,、options和容错处理没有涉及。

// lib/react-redux/connect
import React from 'react'
import Context from './context';

const connect = (mapStateToProps=()=>{}, mapDispatchToProps, mergeProps, options) => (WrappedComp) => {
  return class extends React.Component {
      constructor (props) {
          super(props)
          this.state = {
              allProps: {},     // 所有的属性,包括ownProps, stateProps, dispatchProps
              ownProps: props   // 父组件接受的属性
          }
      }
    	// 声明静态属性 contextType,就可以通过this.context得到Provider提供的store
      static contextType = Context;

      componentDidMount () {
          const store = this.context;
          // didMount时执行update设置allProps,每当state更新都要执行update,保证组件得到最新的allProps
          store.subscribe(this.update)
          this.update()
      }
    
      // 将mapStateToProps和mapDispatchToProps添加到allProps
      update = ()  => {
          const store = this.context;
          const { dispatch } = store;
          let stateProps = {}
          if(mapStateToProps){
            // 将state传给mapStateToProps即可得到stateProps,第二个参数是ownProps,Link就是根据自己的filter属性过滤todos,所有加上了ownProps
            stateProps = mapStateToProps(store.getState(), this.state.ownProps)
          }
          let dispatchProps = {}
          if(typeof mapDispatchToProps === 'object'){
            // 对象形式的mapDispatchToProps,需要将key对应的value传入dispatch,再封装一层
            const keys = Object.keys(mapDispatchToProps)
            keys.forEach(key => {
                dispatchProps[key] = (...args) => {
                    dispatch(mapDispatchToProps[key](...args))
                }
            })
          } else if(typeof mapDispatchToProps === 'function') {
            // 函数形式的mapDispatchToProps,将dispatch传给他即可得到dispatchProps 
            dispatchProps = mapDispatchToProps(dispatch, this.state.ownProps);
          }
          else {
            console.error(`${mapDispatchToProps} is neither an"object" nor a "function"!`)
          }
          this.setState({
              allProps: {
                  ...this.state.ownProps,
                  ...stateProps,
                  ...dispatchProps
              }
          })
      }
      render() {
          const { allProps } = this.state;
          return (
           <WrappedComp {...allProps} />
          )
      }
  }
}

export default connect;