ZF_react redux 中间件thunk promise logger applyMiddleware 中间件级联实现
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZF_react redux 中间件thunk promise logger applyMiddleware 中间件级联实现相关的知识,希望对你有一定的参考价值。
redux设计思想
Redux是将整个应用状态存储到一个地方,称为store。
里面保存一颗状态数state tree。
组件可以派发dispatch行为action给store,而不是直接通知其他组件。
其他组件可以通过订阅store中的状态来刷新视图。
Redux三大原则:
- 整个应用的state被存储在一颗object tree中,并且这个object tree只存在于唯一一个store.
- state是只读的,唯一可以改变state的方法就是触发action.action是一个用于描述已发生事件的普通对象,使用纯函数来执行修改,为了描述action如何改变state tree,你需要编写reducer(reducer规范新的state创建)
- 单一数据源的设计让React组件之间的通信更加方便,同时也便于状态的统一管理。
概念
* store,状态管理库,可以当成一个仓库
* state 状态,可以当作仓库里的东西
* action 动作对象,比如要存放东西到仓库,从仓库取出东西等等。
* dispatch 派发 , 比如你想存放东西到仓库,你创建了这个动作对象,你就需要告诉store。你需要执行这个动作对象,存放东西
* reducer 规范新的state的创建流程。比如你dispatch一个action后,store就会调用reduer,将现有仓库里的东西(state)以及你要做的动作(action)告诉reducer,reducer就会通过action判断你要做什么,从而返回新的state。比如存放东西,一开始state是1,后来存放后state变为2.reducer就会返回新的state 2。更新s状态管理库的东西(store中的state)
* subcribe监听,当你dispatch一个动作对象之后,你希望store在更新完仓库里的东西之后,第一时间通知你。就可以通过subcribe进行订阅。就像直播一样,你订阅一个主播,他直播了就会通知你。store也会在第一时间通知你state改变了。
这里有篇简单实现redux
bindActionCreate的实现
将action与store一起绑定。
用法:
每次dispatch的时候都需要调用store.dispatch,比较麻烦。
使用bindActionCreateors可以将store.dispatch与action连接起来。
具体实现:
直接遍历传入的第一个参数对象,将每个值都处理,使用dispatch关联起来,再返回。
可以正常使用。
combineReducer的实现
用来合并reducer的东西。因为store只有一个,很多页面如果都用一个reducer的话,会太多,所以一般将reducer划分开来,然后再合并。conbineReducer就是来实现合并每个页面的reducer的
使用:
使用combineReducers后,state会变成一个对象。combineRedcuers依然返回的是一个函数,接受state和action。
思路:
采用柯里化存储需要合并的reducers。然后重新写一个大型reducers,他依然接受state和action,但他会遍历所有的需要合并的reducers并执行,获取所有的值后合并成一个对象返回。
如上,保存了reducers,重写了返回的函数,依然接受state和action,每次都遍历执行reducers,返回新的对象。
看效果:
两个状态互不影响,这就实现了combineReducers。所有的reducers都会走一遍。
react-redux
用来连接react跟redux,让其用法更加简洁。
Provider, connect等,具体可以了解redux
import React, { useContext, useLayoutEffect, useState, useMemo } from "react";
import { bindActionCreators } from "../redux";
import { ReactReduxContext } from "./";
/**
* 状态变化监听,让组件刷新
* @param {*} mapState 状态映射属性
* @param {*} mapDispatch action映射属性
* @returns
*/
const connect = (mapState, mapDispatch) => (OldCom) => {
return function (props) {
const {
store: { getState, dispatch, subscribe, listeners },
} = useContext(ReactReduxContext);
//把状态映射成属性
const preState = getState();
const stateProps = useMemo(() => mapState(preState), [preState]);
//把action映射成属性
let dispatchProps = useMemo(() => {
if (typeof mapDispatch === "function") {
return mapDispatch(dispatch);
} else if (typeof mapDispatch === "object") {
//对象就直接绑定
return bindActionCreators(mapDispatch);
} else {
//都不是直接把dispatch给他
return { dispatch };
}
}, []);
//模拟类组件的强制刷新,类组件就直接this.setState了
const [, forceUpdate] = useState();
//订阅状态
useLayoutEffect(() => {
//每次变化强制更新组件
const unSubscribe = subscribe((newState) => {
const newStateProps = mapState(newState);
//精准渲染
for (let key in stateProps) {
if (stateProps[key] !== newStateProps[key]) {
//触发更新之后,会重新渲染组件,重新执行connect,外部的statePorps得到更新。
forceUpdate({});
break;
}
}
});
//记得销毁
return unSubscribe;
}, [stateProps]);
return <OldCom {...stateProps} {...dispatchProps} {...props} />;
};
};
export default connect;
import React from "react";
import { ReactReduxContext } from "./";
const Provider = (props) => {
const { store, children, ...otherProps } = props;
return (
<ReactReduxContext.Provider value={{store}} {...otherProps}>
{children}
</ReactReduxContext.Provider>
);
};
export default Provider
hooks版的实现
useSelector useDispatch
useDispatch直接返回dispatch就行。
useSelector比较麻烦,他跟connect一样,也是用来连接组件和redux的。
接受两个参数,第一个类似于connect的mapStateToProps,第二个参数是一个比对函数。
实现思路:借鉴connect的思想,获取最新的state传入第一个参数,拿到组件想要的props。然后通过UseLayoutEffect来订阅数据的改变,通过比对函数来判断是否要更新。
可以看到跟conncect的实现思路差不多。
在模拟react-redux的shallowEqual
它是用来判断当前组件以来的state是否改变,如果没改变不会刷新,从而优化性能。
正常运行,并且精准渲染。
compose的实现
compose是用来组合函数返回一个新函数。
const fn = compose(a,b,c)
fn(2)
更加简洁的写法:
第一次执行的时候返回一个函数,到最后一个也是返回一个函数,传入的值比如传一个3,如是compose(a,b,c),fn(3 ) => 会先c(3)再b(c(3))再a(b(c(3)))这样获取最终的结果。最终compose返回的是应该是(…args)=>c(b(a(…args))),就像中间件一样,计算后的值一层一层传下去。
中间件
我们现在的redux是dispatch->action->reducer->newState。是同步的,而有时候我们需要异步获取数据,数据获取后再去触发reducer修改state。这时候工作流程就变成action -middlewares-reducer。
先触发发送请求的action,有中间件的话就通过middleware,然后拿到数据之后再触发修改的action,去修改state。
- 中间件的核心原理,就是重写dispatch,在原始的dispatch之前之后,书写自己的逻辑。
最简单的改造
const dispatch = store.dispatch //先存放原始的dispatch
store.dispatch = (action)=> {
//重新指向
setTimeout(()=>{
dispatch(action)
},2000)
}
dispatch被我们重写之后,就支持异步了,它会在两秒后再去触发action。
实现dispatch前后打印state。
先实现一个中间件。
logger就是一个中间件,他获取store的getState和dispatch。
使用:
是不是跟我们平常用的有些出入,这只是一个中间件的写法。
redux-thunk的实现
让dispatch支持函数action.
/**
* 中间件的写法是固定的
* 接受的参数里面的属性分别是getState, dispatch
*/
function thunk({ getState, dispatch }) {
return function (next) {
//next实现中间件的级联,调用下一个中间件
return function (action) {
if (typeof action === "function") {
//函数的话,执行并且输入参数
return action(dispatch, getState);
}
return next(action);
};
};
}
如果是函数的话,
const action = (data) => (dispatch, getState) => {
dispatch({...})
}
dispatch(action(123))
如:
会在两秒后才执行。
redux-promise的实现
/**
* 中间件的写法是固定的
* 接受的参数里面的属性分别是getState, dispatch
* action有两种,一种是action.payload为Promise,一种是直接返回一个Promise
*/
function promise({ getState, dispatch }) {
return function (next) {
//next实现中间件的级联,调用下一个中间件
return function (action) {
if (action.payload && action.payload instanceof Promise) {
action.payload
.then((res) => dispatch({ ...action, payload: res }))
.catch((error) => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
});
} else if (action instanceof Promise) {
action.then(dispatch).reject(dispatch);
}
return next(action);
};
};
}
export default promise;
promise的action有两种写法,所以做两个判断。思路跟thunk是一样的。
如:
两s后渲染
直接返回一个promise也行。
实现中间件级联
现在我们的中间件是单独用的,借助compose将他们级联起来。
这层compose(…middle)(store.dispatch)就是关键实现,他最终返回的是一个dispatch,该dispatch接受promise,参数next就是下一个中间件thunk的dispatch,以此类推。
如图,dispatch的流程,从promise->thunk->logger->store.dispatch
效果:
触发promise
先走promise,.then之后再继续往下走。可以看到顺序是一样的。
现在级联就实现完毕了。但是跟真正的redux的写法有点出入,让我们改造一下。
改造redux实现官网的效果
这样写。
enhancer就是applyMiddleware([xxx]),跟我们第一种写法一摸一样。
又走到createStore,但是此时没有enhancer,所以直接创建了store。
实现完毕!
以上是关于ZF_react redux 中间件thunk promise logger applyMiddleware 中间件级联实现的主要内容,如果未能解决你的问题,请参考以下文章