当前位置: 首页 > news >正文

人人能读懂redux原理剖析

一、Redux是什么?

众所周知,Redux最早运用于React框架中,是一个全局状态管理器。Redux解决了在开发过程中数据无限层层传递而引发的一系列问题,因此我们有必要来了解一下Redux到底是如何实现的?

二、Redux的核心思想?

在这里插入图片描述

Redux主要分为几个部分:dispatchreducerstate
我们着重看下dispatch,该方法是Redux流程的第一步,在用户界面中通过执行dispatch,传入相对应的action对象参数,action是一个描述类型的对象,紧接着执行reducer,最后整体返回一个store对象,我们来看下这部分的源码:

// 主函数createStore
// 返回一个store对象
export default function createStore(reducer, preloadedState, enhancer) {// 增强器if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)}if (typeof reducer !== 'function') {throw new Error('Expected the reducer to be a function.')}let currentReducer = reducerlet currentState = preloadedStatelet currentListeners = []let nextListeners = currentListenerslet isDispatching = false// 获取最终的statefunction getState() {if (isDispatching) {throw new Error('You may not call store.getState() while the reducer is executing. ' +'The reducer has already received the state as an argument. ' +'Pass it down from the top reducer instead of reading it from the store.')}return currentState}// dispatch// 参数actionfunction dispatch(action) {// 校验传入的action// action必须是个对象,否则抛出错误信息if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// 检验action对象的必要属性// type属性是action对象必要的属性// 如果传入的action没有type属性,则抛出错误信息if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}try {isDispatching = true// 执行传入的reducer函数// 返回state,给currentState赋值currentState = currentReducer(currentState, action)} finally {// 一个dispatch执行完,还原状态isDispatching = false}// 执行订阅函数队列// dispatch执行的同时会一并执行订阅队列const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}// 返回actionreturn action}// When a store is created, an "INIT" action is dispatched so that every// reducer returns their initial state. This effectively populates// the initial state tree.// 默认执行一次dispatch,做初始化dispatch({ type: ActionTypes.INIT })// 返回一个store对象return {dispatch,subscribe,getState,...}
}

通过源码我们可以基本清楚,通过执行createStore方法,最终会返回一个store对象,该对象主要暴露几个属性,我们主要关注比较常用的:dispatchgetStategetState,看下实际用例:

import createStore from 'redux'// 创建一个reducer
function reducer(state={}, action) {switch(action.type) {case 'TEST':return {...state,test: 'test success'}}
}// 返回store
const store = createStore(reducer, initState={})// 执行dispatch
store.dispatch({type: 'TEST'
})const state = store.getState() // 返回 {test: 'TEST'}

三、Redux中间件原理

接下来我们来探讨Redux的另一个重要组成部分—中间件。什么是Redux的中间件?Redux中间件其实是通过重写createStore来增强和扩展原来的dispatch方法,使其能够在执行dispatch的同时可以同步执行其它方法,比如redux-thunk就是一个处理异步的中间件:

function createThunkMiddleware(extraArgument) {// 中间件规定格式// 闭包返回三层嵌套return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;

下载了中间件,那么我们来看下如何使用中间件:

import createStore, {applyMiddleWare} from 'reduximport reduxThunk from 'redux-thunk'// 创建一个reducerfunction reducer(state={}, action) {    switch(action.type) {        case 'TEST':        return {            ...state,            test: 'test success'        }    }}// 返回store// 中间件作为applyMiddleWare的参数传入createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))

我们会发现,中间件的使用方式是用applyMiddleWare把中间件作为参数传入createStore中,那么applyMiddleWare是如何实现的?在这之前我们先看下createStore方法的第三个参数是什么,我们回看下createStore源码:参考 前端进阶面试题详细解答

export default function createStore(reducer, preloadedState, enhancer) {...// 增强器// 第三个参数是enhancer,也就是我们传入的applyMiddleWareif (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}// 在这里return了enhancer结果// 传入了createStore,reducer,preloadedState// 实际上是重写了createStorereturn enhancer(createStore)(reducer, preloadedState)}...
}

看完了enhancer的实际作用,我们可以弄清楚applyMiddleWare的实现原理,请看源码:

import compose from './compose'// 传入middlewares中间件
export default function applyMiddleware(...middlewares) {// 闭包嵌套返回2个方法return createStore => (...args) => {// 返回storeconst store = createStore(...args)let dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')}// 返回一个对象// 包含getState方法和dispatch方法const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args) // 返回一个全新的dispatch方法,不污染原来的dispatch}// 执行中间件第一层方法// 回顾下中间的格式:({getState, dispatch}) => next => action => next(action)// 这里会比较绕const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一个中间件的函数集合[next => action => next(action), next => action => next(action)]// 使用compose聚合chain函数集合// 返回新的dispatchdispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
}

这里可能会让人很疑惑,不大清楚的童鞋可以先看下中间件的规范写法,这里还有一个重要的函数compose,我们来看下compose怎么处理chain函数集合的,请看源码:

/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */// 传入聚合函数集合
// 集合为:[next => action => next(action), next => action => next(action)]
// 返回一个新的函数: (arg) => arg  
export default function compose(...funcs) {// 判断如果没有则返回一个新函数// 可以联想一下dispatch的定义// function dispatch(action) {...return action}if (funcs.length === 0) {return arg => arg}// 判断如果只有一个中间件,则直接返回第一个if (funcs.length === 1) {return funcs[0]}// 这里用了reduce函数// 把后一个的中间件的结果当成参数传递给下一个中间件// 函数列表的每个函数执行后返回的还是一个函数:action => next(action)// 这个函数就是新的dispatch// 最后返回函数:(...args) => action => args(action)return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose的源码及其简洁,但是很精髓,几乎是整个中间件最出彩的地方。通过reduce把每个中间件都执行一遍,并且是通过管道式的传输,把每个中间件的返回结果当成参数传递给下一个中间件,实现了剥洋葱式的中间件模式。这里比较难理解,新手可以先写几个简单的中间件,然后再去慢慢理解为什么要这么处理,理解后就会知道这段代码有多简洁了。

四、手写一个Redux

源码解析完了,我们来简单实现一个redux

createStore

// 判断值是否是对象类型
function isPlainObject(obj) {if(!obj) {reutrn false}return Object.prototype.toString.call(obj) === '[object, Object]'
}export default createStore(reducer, enhancer) {// 先判断有没有传入中间件// 有则之间返回if(typeof enhancer !== 'undefined') {// 必需是个函数// 因为需要传参if(typeof enhancer !== 'function') {return}return enhancer(createStore)(reducer)}let state = {} // 初始化statelet listeners = [] // 发布订阅函数队列// 定义getState 函数function getState() {// 直接返回statereturn state}// 定义dispatch 函数function dispatch(action) {try{// 执行reducer, 返回statestate = reducer(state, action)}catch(e) {console.log('dispatch error: 'e)} // 订阅listeners.forEach(listener => listener())// 返回actionreturn action}// 定义subscribe 函数function subscribe(listener) {if(!listener) {return}// 必需是回掉函数// 因为需要在dispatch里执行if(typeof listener !== 'function') {return}Listeners.push(listener)}// 返回对象:包含getState, dispatch, subscribe 三个方法return {getState,dispatch,subscribe}
}

compose

    function compose(...funs) {if(!funs) {return arg => arg}if(funs.length === 1) {return funs[0]}// 遍历传入函数,返回一个新函数return funs.reduce((a,b) => (...args) => a(b(...args)))}

applyMiddleWare

import compose from './compose'function applyMiddleWare(...middlewares) {return createStore => reducer => {// 先返回一个storeconst store = createStore(reducer)// 创建middleApiconst middleApi = {getState: store.getState,dispatch: (...args) => store.dispatch(...args) // 返回一个新的dispatch}// 注入middleApi// 并返回函数集合const chain = middlewares.map(middleWare => middleWare(middleApi))// 通过compose函数,执行所有中间件,并返回一个新的dispatchconst dispatch = compose(...chain)(store.dispatch)// 返回store对象return {getState: store.getState,dispatch}}
}

logger中间件

    function logger({getState, dispatch}) {return function(next) {return function(action) {console.log('prev')next(action)console.log('done')}}}

测试

    import createStore from './myCreateStore'import applyMiddleWare from './myApplyMiddleWare'import logger from './logger'// 创建reducerfunction reducer(state={}, action) {switch(action.type) {case 'TEST':return {...state,test: 'test success'}}}// 引入中间件const middleware = applyMiddleWare(logger)const store = createStore(reducer, middleware) // 返回{getState, dispatch}

总结

至此一个完整的redux我们就已经分析完了,个人认为中间件的compose这里是比较不好理解的点,但是只要明白中间件主要要解决的是增强dispatch函数,就可以顺着这个思路去理解。接着再试着写几个中间件,进一步理解为什么中间件的格式需要返回嵌套的三层函数,明白了这两个点,redux的原理也就基本能够明白了,有问题欢迎在评论中指出。

相关文章:

人人能读懂redux原理剖析

一、Redux是什么&#xff1f; 众所周知&#xff0c;Redux最早运用于React框架中&#xff0c;是一个全局状态管理器。Redux解决了在开发过程中数据无限层层传递而引发的一系列问题&#xff0c;因此我们有必要来了解一下Redux到底是如何实现的&#xff1f; 二、Redux的核心思想…...

uniCloud云开发----7、uniapp通过uni-swiper-dot实现轮播图

uniapp通过uni-swiper-dot实现轮播图前言效果图1、官网实现的效果2、需求中使用到的效果图官网提供的效果图源码1、html部分2、js部分3、css部分根据需求调整轮播图前言 uni-swiper-dot.文档 uni-swiper-dot 轮播图指示点 - DCloud 插件市场 本次展示根据需求制作的和官网用到…...

IM即时通讯构建企业协同生态链

在当今互联网信息飞速发展的时代&#xff0c;随着企业对协同办公要求的提高&#xff0c;协同办公的定义提升到了智能化办公的范畴。大多企业都非常重视构建连接用户、员工和合作伙伴的生态平台&#xff0c;利用即时通讯软件解决企业内部的工作沟通、信息传递和知识共享等问题。…...

Python实现构建gan模型, 输入一个矩阵和两个参数值,输出一个矩阵

构建一个GAN模型,使用Python实现,该模型将接受一个矩阵和两个参数值作为输入,并输出另一个矩阵。GAN(生成对抗网络)是一种深度学习模型,由生成器和判别器两部分组成,可以用于生成具有一定规律性的数据,如图像或音频。 # 定义生成器 def make_generator(noise_dim, dat…...

开学准备哪些电容笔?ipad触控笔推荐平价

在现代&#xff0c;数码产品的发展受到高技术的驱动。不管是在工作上&#xff0c;还是在学习上&#xff0c;大的显示屏可以使图像更加清晰。Ipad将成为我们日常生活中不可或缺的一部分&#xff0c;无论现在或将来。如果ipad配上一款方便操作的电容笔&#xff0c;将极大地提高我…...

放下和拿起 解放自己

放下太难&#xff0c;从过去中解放自己 工作这么久了&#xff0c;第一次不拿包上班&#xff0c;真爽 人的成长都是在碰撞和摸索中产生的&#xff0c;通过摸索&#xff0c;知道自己能力的边界和欲望的边界以及身体的边界&#xff0c;这三个决定了 你能做什么 你能享受什么&…...

100%BIM学员的疑惑:不会CAD可以学Revit吗?

在新一轮科技创新和产业变革中&#xff0c;信息化与建筑业的融合发展已成为建筑业发展的方向&#xff0c;将对建筑业发展带来战略性和全局性的影响。 建筑业是传统产业&#xff0c;推动建筑业科技创新&#xff0c;加快推进信息化发展&#xff0c;激发创新活力&#xff0c;培育…...

经常会采坑的javascript原型应试题

一&#xff0e; 前言 原型和原型链在面试中历来备受重视&#xff0c;经常被提及。说难可能也不太难&#xff0c;但要真正完全理解&#xff0c;吃透它&#xff0c;还是要多下功夫的。 下面为大家简单阐述我对原型和原型链的理解&#xff0c;若是觉得有说的不对的地方&#xff…...

完全背包—动态规划

一、背包问题概述 如图&#xff0c;完全背包与01背包的区别只有一点&#xff1a;01背包中每个物品只能取一个而完全背包中每个物品可以取无数个。解决完全背包问题必须首先弄明白01背包&#xff0c;不清楚的可以看我的这篇文章01背包—动态规划。 二、例题 重量价值物品0115物…...

消息队列MQ介绍

消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上,队列存储消息直到它们被应用程序读走。通过消息队列&#xff0c;应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。 消息中间件概述 消息队列技术是…...

C语言进阶(八)—— 链表

1. 链表基本概念1.1 什么是链表链表是一种常用的数据结构&#xff0c;它通过指针将一些列数据结点&#xff0c;连接成一个数据链。相对于数组&#xff0c;链表具有更好的动态性&#xff08;非顺序存储&#xff09;。数据域用来存储数据&#xff0c;指针域用于建立与下一个结点的…...

手工测试用例就是自动化测试脚本——使用ruby 1.9新特性进行自动化脚本的编写

昨天因为要装watir-webdriver的原因将用了快一年的ruby1.8.6升级到了1.9。由于1.9是原生支持unicode编码&#xff0c;所以我们可以使用中文进行自动化脚本的编写工作。 做了简单的封装后&#xff0c;我们可以实现如下的自动化测试代码。请注意&#xff0c;这些代码是可以正确运…...

RockerMQ简介和单节点部署

目录一、RockerMQ简介二、Linux中单节点部署1、准备工作2、下载和解压3、修改初始内存4、启动5、查看进程6、发送接收消息测试7、关闭三、控制台的安装与启动(可视化页面)1、修改配置&#xff08;1&#xff09;修改端口号&#xff08;2&#xff09;指定RocketMQ的name server地…...

SFP光纤笼子 别称 作用 性能要点 工程要素

Hqst盈盛电子导读&#xff1a;2023年&#xff0c;Hqst盈盛电子于下属五金部开发生产SFP光纤连接器笼子等系列产品&#xff0c;所有产品生产及性标准都将参照连接器产品常用测试标准EIA-364-C等标准&#xff0c;以下为我司常规SFP光纤连接器基本性能要求SFP光纤笼子别称&#xf…...

[HarekazeCTF2019]Easy Notes

知识点&#xff1a;session 反序列化&#xff0c;代码审计代码分析 flag.php 中有个 is_admin 函数的判断。 在 lib.php 中有 is_admin 函数&#xff0c;需要 session[admin] 为 true&#xff0c;或者通过文件读取的方式。 在 index.php 中的 include 并不能使用伪协议读取 …...

Java学习-IO流-字符流-FileReader

Java学习-IO流-字符流-FileReader 字符流 字节流 字符集 输入流&#xff1a;默认一次读一个字节&#xff0c;遇到中文时一次读多个字节 输出流&#xff1a;底层把数据按照指定编码方式编码&#xff0c;变成字节写入文件 使用场景&#xff1a;纯文本文件读写 // …...

python攻陷米哈游《元神》数据?详情请看文章。。

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 《原神》是由米哈游自研的一款全新开放世界冒险RPG。 里面拥有许多丰富得角色&#xff0c;让玩家为之着迷~ 今天&#xff0c;我们就来用python探索一下原神游戏角色信息&#xff01; 标题大家看看就好了哈~&#xff08…...

【unity细节】基于unity子对象(如相机)为什么无法进行z轴的拖拽移动和z轴自动归位的问题

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐基于unity子对象为什么无法进行z轴的拖拽移动和z轴自动归位⭐ 文章目录⭐基于u…...

如何维护固态继电器?

固态继电器是SSR的简称&#xff0c;是由微电子电路、分立电子器件和电力电子功率器件组成的非接触式开关。隔离装置用于实现控制端子与负载终端之间的隔离。固态继电器的输入端使用微小的控制信号直接驱动大电流负载。那么&#xff0c;如何保养固态继电器呢&#xff1f; 在为小…...

Sprng依赖注入(三):构造方法注入是如何工作的?

前言这是Spring依赖注入系列的第三篇&#xff0c;前两篇主要分析了Spring bean依赖属性注入的两种方式&#xff0c;是字段注入和setter方法注入&#xff0c;单独比较这两种方式&#xff0c;会发现其过程和工作原理非常类似&#xff0c;那么构造方法注入会不会也和前两种比较类似…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...