Provider组件
Provider用于建立能够被子组件访问的全局属性,核心API有两个:
- childContextTypes静态属性,用于指定被子组件访问的全局属性类型
- getChildContext方法,用于指定可以被子组件访问的全局属性
Provider简版源码实现:
import React from 'react';
import PropTypes from 'prop-types';
export default class Provider extends React.Component{
//指定子组件可以访问的属性类型
static childContextTypes = {
store: PropTypes.object
}
constructor(props){
super(props);
}
//指定子组件可以访问的属性
getChildContext(){
return{
store: this.props.store
}
}
render() {
//用其包含的子组件进行视图渲染
return this.props.children;
}
}
然后,在引用到全局属性的子组件当中指定contextTypes进行指定的全局属性获取,同时需要在子组件的构造函数中声明context,否则context是一个空对象
static contextTypes = {
store: PropTypes.object
}
constructor(props,context){
super(props,context);
}
最后,把Provider组件作为根组件,传入相应相对应的全局属性即可:
<Provider store={store}>
<App></App>
</Provider>
connect函数
connect函数其实是一个代理模式的高阶组件,对目标组件进行增强或减弱形成一个新的组件,然后返回这个新的组件。
connect函数有两个参数,分别是mapStateToProps和mapDispatchToProps。
mapStateToProps
mapStateToProps是一个匿名函数,用于指定传递到组件当中的props,实现方式也简单,直接执行其匿名函数即可:
const stateProps = mapStateToProps(store.getState());
最后,把stateProps展开传进目标组件即可
mapDispatchToProps
mapDispatchToProps既可以是对象,也可以是函数,它主要是用于action和传进来的props进行关联,利用bindActionCreators函数对action进行了dispatch的封装,使action变成dispatch(action),形成派发指定的action对指定的props进行调用。
const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch);
//把action封装成dispatch(action)
function bindActionCreators(actionCreators,dispatch) {
return Object.keys(actionCreators).reduce((result,item) => {
result[item] = (...args) => dispatch(actionCreators[item](...args));
return result;
},{})
}
最后,把dispatchProps展开传进目标组件即可
connect函数的完整实现
import React from 'react';
import PropTypes from 'prop-types';
import {bindActionCreators} from './mini-redux';
export const connect = (mapStateToProps = state => state, mapDispatchToProps={}) => (WrapComponent) => {
class ConnectComponent extends React.Component{
//指定要访问的全局属性,若contextTypes没有定义,context将是一个空对象
static contextTypes = {
store: PropTypes.object
}
constructor(props,context){
super(props,context);
this.state = {
props: {}
}
this.update = this.update.bind(this);
}
componentDidMount(){
const {store} = this.context;
store.subscribe(this.update);//订阅store里的状态,发生变化就调用update函数
this.update();
}
componentWillUnmount(){
const {store} = this.context;
store.unsubscribe(this.update);//移除监听stor状态变化的函数
}
//获取mapStateToProps和mapDispatchToProps,放进this.state.props中更新组件
update(){
const {store} = this.context;
const stateProps = mapStateToProps(store.getState());//获取传进来state
const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch);//把action封装成dispatch(action)
this.setState({
props:{
...this.state.props,
...stateProps,
...dispatchProps
}
})
}
render(){
return(
//把传递进来的state和封装后的dispatch(action)作为props传到目标组件
<WrapComponent {...this.state.props}></WrapComponent>
)
}
}
return ConnectComponent;
}
中间件
中间件利用applyMiddleWare来增强createStore函数,类似于装饰器模式,先来看看createStore函数的简版实现
createStore函数
export function createStore(reducer,enhancer) {
if(enhancer){
//如果存在store增强器,就对createStore进行封装
return enhancer(createStore)(reducer);
}
let currentState = {};//保存项目当前的state,默认为空对象
let currentListeners = [];//用来存放监听store变化的函数
//用来获取当前的state
function getState() {
return currentState;
}
//派发action
function dispatch(action) {
currentState = reducer(currentState,action);//使用reducer处理当前派发过来的action
currentListeners.forEach(currentListener => currentListener());//处理完成后返回新的state触发store的改变,遍历监听store变化的函数并执行使组件发生变化
return action;
}
//增加监听store变化的函数
function subscribe(listener) {
currentListeners.push(listener);
}
//移除取消监听store变化的函数
function unsubscribe(listener) {
currentListeners = currentListeners.filter(l => l !== listener)
}
dispatch({type:'xxxx'});//触发初次调用,type可以设任意值,但是必须要走default的case,获取默认值传给组件初始化
return {getState,dispatch,subscribe,unsubscribe};//返回store内部的三个函数供外部调用
}
applyMiddleWare函数
上面看到存在store增强器,其实也就是applyMiddleWare函数,返回两层嵌套的函数,主要是针对store的dispatch方法进行增强,让dispatch的action不是直接到达reducer函数进行处理,而是经过层层的中间件,所以applyMiddleWare函数简版实现如下:
function applyMiddleWare(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);//根据reducer和初始值创建原生store
let dispatch = store.dispatch;//获取原生store的dispatch函数
//中间件是嵌套了两层函数的函数,接收getState和dispatch作为参数
const middlewareApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const middlewareChain = middlewares.map(middleware => middleware(middlewareApi));//获取传递进来的中间件,并且初始化中间件函数
dispatch = compose(...middlewareChain)(store.dispatch);//组合多个中间件,并且把原生的dispatch放在作为最后一个中间件的dispatch参数
//单个中间件的做法:dispatch = middleware(middlewareApi)(store.dispatch);
//返回经过增强dispatch函数的store
return{
...store,
dispatch
}
}
}
compose函数
compose函数是把多个中间件组合在一起,把compose(fn1,fn2,fn3)转换为fn1(fn2(fn3))的形式,函数从右往左执行,其实就是下一个函数执行结果作为前一个函数的参数,对应在这里就是把下一个中间件作为前一个中间函数的next参数,compose的简版源码如下:
function compose(...fns){
if(fns.length === 0){
return arg => arg
}
else if(fns.length === 1){
return fns[0]
}
else{
//返回结果:(...args) = > fn1(fn2(fn3(...args)))
return fns.reduce((result,item) => (...args) => result(item(...args)));
}
}
thunk中间件简版实现
const thunk = ({dispatch,getState}) => next => action => {
//如果action是一个函数,就执行该函数
if(typeof action === 'function'){
return action(dispatch,getState);
}
//否则,如果当前中间件不是最后一个中间件,就把action传递到下一个中间件中执行
//如果当前中间件已经是最后一个中间件,那么next就是store上原生的dispatch,直接给到reducer处理了
return next(action);
}
export default thunk;
定制中间件
我们尝试手动定制一个中间件arrayThunk,判断action是否为数组类型,如果为数组类型,就遍历数组,把数组中的元素作为action进行重新派发:
const arrayThunk = ({dispatch,getState}) => next => action => {
if(Array.isArray(action)){
return action.forEach(item => dispatch(item));
}
return next(action);
}
export default arrayThunk;
正常调用即可:
const store = createStore(count,applyMiddleWare(thunk,arrayThunk));