简介
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;