范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

手写一个Redux,深入理解其原理

  Redux可是一个大名鼎鼎的库,很多地方都在用,我也用了几年了,今天这篇文章就是自己来实现一个Redux,以便于深入理解他的原理。我们还是老套路,从基本的用法入手,然后自己实现一个Redux来替代源码的NPM包,但是功能保持不变。本文只会实现Redux的核心库,跟其他库的配合使用,比如React-Redux准备后面单独写一篇文章来讲。有时候我们过于关注使用,只记住了各种使用方式,反而忽略了他们的核心原理,但是如果我们想真正的提高技术,最好还是一个一个搞清楚,比如Redux和React-Redux看起来很像,但是他们的核心理念和关注点是不同的,Redux其实只是一个单纯状态管理库,没有任何界面相关的东西,React-Redux关注的是怎么将Redux跟React结合起来,用到了一些React的API。基本概念
  Redux的概念有很多文章都讲过,想必大家都看过很多了,我这里不再展开,只是简单提一下。Redux基本概念主要有以下几个:Store
  人如其名,Store就是一个仓库,它存储了所有的状态(State),还提供了一些操作他的API,我们后续的操作其实都是在操作这个仓库。假如我们的仓库是用来放牛奶的,初始情况下,我们的仓库里面一箱牛奶都没有,那Store的状态(State)就是:{ 	milk: 0 } Actions
  一个Action就是一个动作,这个动作的目的是更改Store中的某个状态,Store还是上面的那个仓库,现在我想往仓库放一箱牛奶,那"我想往仓库放一箱牛奶"就是一个Action,代码就是这样:{   type: "PUT_MILK",   count: 1 } Reducers
  前面"我想往仓库放一箱牛奶"只是想了,还没操作,具体操作要靠Reducer,Reducer就是根据接收的Action来改变Store中的状态,比如我接收了一个PUT_MILK,同时数量count是1,那放进去的结果就是milk增加了1,从0变成了1,代码就是这样:const initState = {   milk: 0 }  function reducer(state = initState, action) {   switch (action.type) {     case "PUT_MILK":       return {...state, milk: state.milk + action.count}     default:       return state   } }
  可以看到Redux本身就是一个单纯的状态机,Store存放了所有的状态,Action是一个改变状态的通知,Reducer接收到通知就更改Store中对应的状态。简单例子
  下面我们来看一个简单的例子,包含了前面提到的Store,Action和Reducer这几个概念:import { createStore } from "redux";  const initState = {   milk: 0 };  function reducer(state = initState, action) {   switch (action.type) {     case "PUT_MILK":       return {...state, milk: state.milk + action.count};     case "TAKE_MILK":       return {...state, milk: state.milk - action.count};     default:       return state;   } }  let store = createStore(reducer);  // subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用 // 如果是结合页面更新,更新的操作就是在这里执行 store.subscribe(() => console.log(store.getState()));  // 将action发出去要用dispatch store.dispatch({ type: "PUT_MILK" });    // milk: 1 store.dispatch({ type: "PUT_MILK" });    // milk: 2 store.dispatch({ type: "TAKE_MILK" });   // milk: 1 自己实现
  前面我们那个例子虽然短小,但是已经包含了Redux的核心功能了,所以我们手写的第一个目标就是替换这个例子中的Redux。要替换这个Redux,我们得先知道他里面都有什么东西,仔细一看,我们好像只用到了他的一个API:
  createStore:这个API接受reducer方法作为参数,返回一个store,主要功能都在这个store上。
  看看store上我们都用到了啥:store.subscribe: 订阅state的变化,当state变化的时候执行回调,可以有多个subscribe,里面的回调会依次执行。
  store.dispatch: 发出action的方法,每次dispatch action都会执行reducer生成新的state,然后执行subscribe注册的回调。
  store.getState:一个简单的方法,返回当前的state。
  看到subscribe注册回调,dispatch触发回调,想到了什么,这不就是发布订阅模式吗?我之前有一篇文章详细讲过发布订阅模式了,这里直接仿写一个。function createStore() {   let state;              // state记录所有状态   let listeners = [];     // 保存所有注册的回调    function subscribe(callback) {     listeners.push(callback);       // subscribe就是将回调保存下来   }    // dispatch就是将所有的回调拿出来依次执行就行   function dispatch() {     for (let i = 0; i < listeners.length; i++) {       const listener = listeners[i];       listener();     }   }    // getState直接返回state   function getState() {     return state;   }    // store包装一下前面的方法直接返回   const store = {     subscribe,     dispatch,     getState   }    return store; }
  上述代码是不是很简单嘛,Redux核心也是一个发布订阅模式,就是这么简单!等等,好像漏了啥,reducer呢?reducer的作用是在发布事件的时候改变state,所以我们的dispatch在执行回调前应该先执行reducer,用reducer的返回值重新给state赋值,dispatch改写如下:function dispatch(action) {   state = reducer(state, action);    for (let i = 0; i < listeners.length; i++) {     const listener = listeners[i];     listener();   } }
  到这里,前面例子用到的所有API我们都自己实现了,我们用自己的Redux来替换下官方的Redux试试:// import { createStore } from "redux"; import { createStore } from "./myRedux";
  可以看到输出结果是一样的,说明我们自己写的Redux没有问题:
  了解了Redux的核心原理,我们再去看他的源码应该就没有问题了,createStore的源码传送门。
  最后我们再来梳理下Redux的核心流程,注意单纯的Redux只是个状态机,是没有View层的哦。
  除了这个核心逻辑外,Redux里面还有些API也很有意思,我们也来手写下。手写combineReducers
  combineReducers也是使用非常广泛的API,当我们应用越来越复杂,如果将所有逻辑都写在一个reducer里面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers,可以让我们为不同的模块写自己的reducer,最终将他们组合起来。比如我们最开始那个牛奶仓库,由于我们的业务发展很好,我们又增加了一个放大米的仓库,我们可以为这两个仓库创建自己的reducer,然后将他们组合起来,使用方法如下:import { createStore, combineReducers } from "redux";  const initMilkState = {   milk: 0 }; function milkReducer(state = initMilkState, action) {   switch (action.type) {     case "PUT_MILK":       return {...state, milk: state.milk + action.count};     case "TAKE_MILK":       return {...state, milk: state.milk - action.count};     default:       return state;   } }  const initRiceState = {   rice: 0 }; function riceReducer(state = initRiceState, action) {   switch (action.type) {     case "PUT_RICE":       return {...state, rice: state.rice + action.count};     case "TAKE_RICE":       return {...state, rice: state.rice - action.count};     default:       return state;   } }  // 使用combineReducers组合两个reducer const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});  let store = createStore(reducer);  store.subscribe(() => console.log(store.getState()));  // 操作的action store.dispatch({ type: "PUT_MILK", count: 1 });    // milk: 1 store.dispatch({ type: "PUT_MILK", count: 1 });    // milk: 2 store.dispatch({ type: "TAKE_MILK", count: 1 });   // milk: 1  // 操作大米的action store.dispatch({ type: "PUT_RICE", count: 1 });    // rice: 1 store.dispatch({ type: "PUT_RICE", count: 1 });    // rice: 2 store.dispatch({ type: "TAKE_RICE", count: 1 });   // rice: 1
  上面代码我们将大的state分成了两个小的milkState和riceState,最终运行结果如下:
  知道了用法,我们尝试自己来写下呢!要手写combineReducers,我们先来分析下他干了啥,首先它的返回值是一个reducer,这个reducer同样会作为createStore的参数传进去,说明这个返回值是一个跟我们之前普通reducer结构一样的函数。这个函数同样接收state和action然后返回新的state,只是这个新的state要符合combineReducers参数的数据结构。我们尝试来写下:function combineReducers(reducerMap) {   const reducerKeys = Object.keys(reducerMap);    // 先把参数里面所有的键值拿出来      // 返回值是一个普通结构的reducer函数   const reducer = (state = {}, action) => {     const newState = {};          for(let i = 0; i < reducerKeys.length; i++) {       // reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值       // 然后将所有reducer返回的state按照参数里面的key组装好       // 最后再返回组装好的newState就行       const key = reducerKeys[i];       const currentReducer = reducerMap[key];       const prevState = state[key];       newState[key] = currentReducer(prevState, action);     }          return newState;   };      return reducer; }
  官方源码的实现原理跟我们的一样,只是他有更多的错误处理,大家可以对照着看下。手写applyMiddleware
  middleware是Redux里面很重要的一个概念,Redux的生态主要靠这个API接入,比如我们想写一个logger的中间件可以这样写(这个中间件来自于官方文档):// logger是一个中间件,注意返回值嵌了好几层函数 // 我们后面来看看为什么这么设计 function logger(store) {   return function(next) {     return function(action) {       console.group(action.type);       console.info("dispatching", action);       let result = next(action);       console.log("next state", store.getState());       console.groupEnd();       return result     }   } }  // 在createStore的时候将applyMiddleware作为第二个参数传进去 const store = createStore(   reducer,   applyMiddleware(logger) )
  可以看到上述代码为了支持中间件,createStore支持了第二个参数,这个参数官方称为enhancer,顾名思义他是一个增强器,用来增强store的能力的。官方对于enhancer的定义如下:type StoreEnhancer = (next: StoreCreator) => StoreCreator
  上面的结构的意思是说enhancer作为一个函数,他接收StoreCreator函数作为参数,同时返回的也必须是一个StoreCreator函数。注意他的返回值也是一个StoreCreator函数,也就是我们把他的返回值拿出来继续执行应该得到跟之前的createStore一样的返回结构,也就是说我们之前的createStore返回啥结构,他也必须返回结构,也就是这个store:{   subscribe,   dispatch,   getState } createStore支持enhancer
  根据他关于enhancer的定义,我们来改写下自己的createStore,让他支持enhancer:function createStore(reducer, enhancer) {   // 接收第二个参数enhancer   // 先处理enhancer   // 如果enhancer存在并且是函数   // 我们将createStore作为参数传给他   // 他应该返回一个新的createStore给我   // 我再拿这个新的createStore执行,应该得到一个store   // 直接返回这个store就行   if(enhancer && typeof enhancer === "function"){     const newCreateStore = enhancer(createStore);     const newStore = newCreateStore(reducer);     return newStore;   }      // 如果没有enhancer或者enhancer不是函数,直接执行之前的逻辑   // 下面这些代码都是之前那版   // 省略n行代码 	// .......   const store = {     subscribe,     dispatch,     getState   }    return store; }
  这部分对应的源码看这里。applyMiddleware返回值是一个enhancer
  前面我们已经有了enhancer的基本结构,applyMiddleware是作为第二个参数传给createStore的,也就是说他是一个enhancer,准确的说是applyMiddleware的返回值是一个enhancer,因为我们传给createStore的是他的执行结果applyMiddleware():function applyMiddleware(middleware) {   // applyMiddleware的返回值应该是一个enhancer   // 按照我们前面说的enhancer的参数是createStore   function enhancer(createStore) {     // enhancer应该返回一个新的createStore     function newCreateStore(reducer) {       // 我们先写个空的newCreateStore,直接返回createStore的结果       const store = createStore(reducer);       return store     }          return newCreateStore;   }      return enhancer; } 实现applyMiddleware
  上面我们已经有了applyMiddleware的基本结构了,但是功能还没实现,要实现他的功能,我们必须先搞清楚一个中间件到底有什么功能,还是以前面的logger中间件为例:function logger(store) {   return function(next) {     return function(action) {       console.group(action.type);       console.info("dispatching", action);       let result = next(action);       console.log("next state", store.getState());       console.groupEnd();       return result     }   } }
  这个中间件运行效果如下:
  可以看到我们let result = next(action);这行执行之后state改变了,前面我们说了要改变state只能dispatch(action),所以这里的next(action)就是dispatch(action),只是换了一个名字而已。而且注意最后一层返回值return function(action)的结构,他的参数是action,是不是很像dispatch(action),其实他就是一个新的dispatch(action),这个新的dispatch(action)会调用原始的dispatch,并且在调用的前后加上自己的逻辑。所以到这里一个中间件的结构也清楚了:
  一个中间件接收store作为参数,会返回一个函数返回的这个函数接收老的dispatch函数作为参数,会返回一个新的函数返回的新函数就是新的dispatch函数,这个函数里面可以拿到外面两层传进来的store和老dispatch函数
  所以说白了,中间件就是加强dispatch的功能,用新的dispatch替换老的dispatch,这不就是个装饰者模式吗?其实前面enhancer也是一个装饰者模式,传入一个createStore,在createStore执行前后加上些代码,最后又返回一个增强版的createStore。可见设计模式在这些优秀的框架中还真是广泛存在,如果你对装饰者模式还不太熟悉,可以看我之前这篇文章。
  遵循这个思路,我们的applyMiddleware就可以写出来了:// 直接把前面的结构拿过来 function applyMiddleware(middleware) {   function enhancer(createStore) {     function newCreateStore(reducer) {       const store = createStore(reducer);              // 将middleware拿过来执行下,传入store       // 得到第一层函数       const func = middleware(store);              // 解构出原始的dispatch       const { dispatch } = store;              // 将原始的dispatch函数传给func执行       // 得到增强版的dispatch       const newDispatch = func(dispatch);              // 返回的时候用增强版的newDispatch替换原始的dispatch       return {...store, dispatch: newDispatch}     }          return newCreateStore;   }      return enhancer; }
  照例用我们自己的applyMiddleware替换老的,跑起来是一样的效果,说明我们写的没问题,哈哈~
  支持多个middleware
  我们的applyMiddleware还差一个功能,就是支持多个middleware,比如像这样:applyMiddleware(   rafScheduler,   timeoutScheduler,   thunk,   vanillaPromise,   readyStatePromise,   logger,   crashReporter )
  其实要支持这个也简单,我们返回的newDispatch里面依次的将传入的middleware拿出来执行就行,多个函数的串行执行可以使用辅助函数compose,这个函数定义如下。只是需要注意的是我们这里的compose不能把方法拿来执行就完了,应该返回一个包裹了所有方法的方法。function compose(...func){   return funcs.reduce((a, b) => (...args) => a(b(...args))); }
  这个compose可能比较让人困惑,我这里还是讲解下,比如我们有三个函数,这三个函数都是我们前面接收dispatch返回新dispatch的方法:const fun1 = dispatch => newDispatch1; const fun2 = dispatch => newDispatch2; const fun3 = dispatch => newDispatch3;
  当我们使用了compose(fun1, fun2, fun3)后执行顺序是什么样的呢?// 第一次其实执行的是 (func1, func2) => (...args) => func1(fun2(...args)) // 这次执行完的返回值是下面这个,用个变量存起来吧 const temp = (...args) => func1(fun2(...args))  // 我们下次再循环的时候其实执行的是 (temp, func3) => (...args) => temp(func3(...args)); // 这个返回值是下面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数 // 前面的返回值会作为后面参数 (...args) => temp(func3(...args));  // 再看看上面这个方法,如果把dispatch作为参数传进去会是什么效果 (dispatch) => temp(func3(dispatch));  // 然后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是下面这个会执行 (newDispatch3) => func1(fun2(newDispatch3))  // 上面这个里面用newDispatch3执行fun2(newDispatch3)会得到newDispatch2 // 然后func1(newDispatch2)会得到newDispatch1 // 注意这时候的newDispatch1其实已经包含了newDispatch3和newDispatch2的逻辑了,将它拿出来执行这三个方法就都执行了
  更多关于compose原理的细节可以看我之前这篇文章。
  所以我们支持多个middleware的代码就是这样:// 参数支持多个中间件 function applyMiddleware(...middlewares) {   function enhancer(createStore) {     function newCreateStore(reducer) {       const store = createStore(reducer);              // 多个middleware,先解构出dispatch => newDispatch的结构       const chain = middlewares.map(middleware => middleware(store));       const { dispatch } = store;              // 用compose得到一个组合了所有newDispatch的函数       const newDispatchGen = compose(...chain);       // 执行这个函数得到newDispatch       const newDispatch = newDispatchGen(dispatch);        return {...store, dispatch: newDispatch}     }          return newCreateStore;   }      return enhancer; }
  最后我们再加一个logger2中间件实现效果:function logger2(store) {   return function(next) {     return function(action) {       let result = next(action);       console.log("logger2");       return result     }   } }  let store = createStore(reducer, applyMiddleware(logger, logger2));
  可以看到logger2也已经打印出来了,大功告成。
  现在我们也可以知道他的中间件为什么要包裹几层函数了:第一层:目的是传入store参数
  第二层:第二层的结构是dispatch => newDispatch,多个中间件的这层函数可以compose起来,形成一个大的dispatch => newDispatch
  第三层:这层就是最终的返回值了,其实就是newDispatch,是增强过的dispatch,是中间件的真正逻辑所在。总结单纯的Redux只是一个状态机,store里面存了所有的状态state,要改变里面的状态state,只能dispatch action。对于发出来的action需要用reducer来处理,reducer会计算新的state来替代老的state。subscribe方法可以注册回调方法,当dispatch action的时候会执行里面的回调。Redux其实就是一个发布订阅模式!Redux还支持enhancer,enhancer其实就是一个装饰者模式,传入当前的createStore,返回一个增强的createStore。Redux使用applyMiddleware支持中间件,applyMiddleware的返回值其实就是一个enhancer。Redux的中间件也是一个装饰者模式,传入当前的dispatch,返回一个增强了的dispatch。单纯的Redux是没有View层的,所以他可以跟各种UI库结合使用,比如react-redux,计划下一篇文章就是手写react-redux。

RTX40804060价格曝光,老黄又秀迷之操作,玩家直呼搞不懂Hello大家好,我是兼容机之家的小牛!最近的这两年的时间里,显卡价格就像坐了一半的过山车只上不下!一千元的显卡两千元卖两千元的显卡四千块钱卖,这已经是常态,而且千辛万苦买到的显卡伊朗发现巨大的里海天然气,引发欧洲和美国的不安上周,伊朗在里海的伊朗地区发现了一个巨大的新天然气矿藏Chalous油田,伊朗可以据其在北部形成一个新的天然气中心,以补充庞大的SouthPars油田的南部天然气中心。Chalou科技创新需要国家队随着互联网时代的快速发展,不断呈现新兴业态,数字经济开始崛起。当互联网物联网云计算大数据5G等新技术不断融合,数字经济将为各行各业全面赋能,成为世界经济的重要增长点和驱动力,这将是黄金重返1800美元关卡上方,市场等待美联储的鸽派信号周一(23日)交易,黄金期货价格收高,因美元走疲的支撑,让价格在2周多以来首次回升到1800美元关键位置的上方。券商Zaner分析师周一的报告中写道美元显着逆转和广泛的心理风险,推中国首次释放战略石油储备,向市场释放了哪些信号?中国宣布将首次出售其国家石油储备中的石油,这是因为不断上涨的原油价格给国内的工业生产带来通胀压力。国家粮食和物资储备局周四表示,将分批向国内炼油和化工企业发放石油,以缓解原材料价格体验不俗概念超前,OPPO这些穿戴黑科技或将带来交互新革命近几年随着人们对于自己健康标准的要求越来越高,像是智能手环智能手表等设备,也逐渐成为大家关注的热点。近日央视财经频道也对于这一现象进行了报道,根据有关数据显示,2020年第三季度,未来科技近在咫尺?商汤科技携手OPPO等企业发布首个AR标准提起AR技术大家最先想到的是什么呢?相信会有不少人想到电影头号玩家中的剧情吧,当我们佩戴上AR眼镜等设备后,就会进入一个完全崭新的世界,在其中我们不仅能休闲娱乐,还能获取知识。而近科技股再次陷入苦战,比亚迪未来可期港股上周二起连跌4日,重磅科技蓝筹全线下滑,拖累恒指全周下泻近750点或2。9,再成全球表现最差股市。中概科技股转弱关键,在于上周一晚突传出中国电信遭华盛顿吊销在美国的营运许可。消手机该不该每天关机一次?维修师傅告诉你晚上习惯关机睡觉的朋友们在哪里?举起你们的手让小宁君看看!那么问题来了,你为啥要关机睡觉?是担心手机睡眠不足,还是担心屏亮会让你忍不住的熬夜?然鹅,有朋友认为手机应该每天关机一次,华为mate50Pro概念图曝光,性能配置不差,科技感十足经过很长时间的发展,苹果在智能手机当中一直处于霸主的地位,并且当有新型iPhone上市时,它的销量都有大概率打破新的纪录。自从步入5G时代后,智能手机的发展形势一直在变,华为展示出新一代安卓机皇花落小米!这次小米11超大杯居然这么猛就在今天,小米总裁雷军的一条微博引来了广大网友的热议。而这条微博的主角正是小米11系列的两位新成员小米11Pro以及小米11Ultra。从雷军的微博中能看出,对于这两款新机,雷军信
想找个副业来增加收入,好迷茫啊?万事开头难,但是不要怕,这是一个无本的生意,做得好赚了,做不好也没损失什么,权当好玩。虽说不要成本但是还是比较花时间的,至于怎么做?头条里面一搜索大把告诉你写头条拍视频的短视频,要2022年3月以后,微信支付宝还能付钱吗?从2022年3月1号开始,个人收款码禁用于经营性服务,禁止个人静态收款码被用于远程非面对面收款,你可能以为它只是一张码的事,但是它背后的大棋局,一定是你想象不到的。首先各位审题能不什么是聚合支付,聚合支付的优势又是什么?所谓聚合支付,就是依托银行三方支付或清算组织的支付通道与清算能力,为客户提供接口集成对接订单处理数据统计等的支付服务机构。通过锋锐的聚合SDK,聚合支付场景聚合支付方式聚合支付通道什么是web3大家好,我是猫神财经,今天说说马斯克挑起的WEB3吧,首先大家要知道什么是web3,简单的解释就是实现上网去中心化,用户更有隐私性,元宇宙是塑造互联网甚至世界未来的概念之一。Web山东林安现代物流的发展趋势呈现物流是现代企业创造价值的一个重要区域,在日益强烈的市场竞争中,企业的发展与生存关键在成本领先。现代物流的发展趋势呈现出全球化多功能化系统化信息化和标准化的特征,其中信息化是现代物流瑞莱智慧徐世真隐私计算商业化落地面临四大挑战未来发展可借鉴两大经验本报记者朱宝琛在数据融合应用和隐私保护的双重驱动下,隐私计算热潮迅速兴起。12月10日,在数据安全与隐私计算论坛上,瑞莱智慧RealAI首席架构师徐世真发表隐私计算助力构建AI新基45岁的中年人还有改变命运的机会吗?不能,如果你到45岁还是底层人,那你的圈子和学识都是非常低的。你整天在为生活奔波,你没有时间去学习,没有机会进入有钱人的圈子。你的思维跟不上有格局的人。改变命运第一要靠自己的努力,元宇宙虚拟房产到底是什么?为什么这么火?会和比特币一样吗?不知道你是属于809000,类似于YY小说网游系列,再简单通俗一点就好比王者荣耀,我们现在是在手机上玩,元宇宙就是更高一级,戴个头盔或者VR眼睛,把你的思想带入到一个虚拟世界里面,锂电池和铅酸电池各有啥特点?整体相对比来说,锂电池优点循环使用次数多铅酸电池优点安全稳定但实际上没有可比性,因为每款电池的性能都不一样。就像天能电池旗下的两款电池,一款锂电,一款铅酸,性能各有优势。天能金刚E联想为什么越来越不行了?明显是友商带节奏的伪命题!联想连续多年的世界pc销量冠军,刚刚过去的第三季度仍然是pc世界销量冠军。如果说这样的销量冠军品牌不行,那么还有几个企业是题主认为行的。毕竟中国企业的产品云闪付为什么没有能普及?它哪里比不过微信支付宝?个人观点,云闪付实用性不够,不能方便使用,许多地方不支持云闪付除了支付功能还能干其他多少事?微信支付宝可以干无数无数的事,然后还能支付。支付宝先入为主,领先一步了。云闪付现在优惠多