人人能读懂redux原理剖析

news2024/10/6 8:39:10

一、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 = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 获取最终的state
  function 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
  // 参数action
  function 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()
    }

    // 返回action
    return 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,也就是我们传入的applyMiddleWare
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    // 在这里return了enhancer结果
    // 传入了createStore,reducer,preloadedState
    // 实际上是重写了createStore
    return enhancer(createStore)(reducer, preloadedState)
  }

  ...
}

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

import compose from './compose'

// 传入middlewares中间件
export default function applyMiddleware(...middlewares) {
  // 闭包嵌套返回2个方法
  return createStore => (...args) => {
      // 返回store
    const 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函数集合
    // 返回新的dispatch
    dispatch = 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 = {} // 初始化state
    let listeners = [] // 发布订阅函数队列

    // 定义getState 函数
    function getState() {
        // 直接返回state
        return state
    }

    // 定义dispatch 函数
    function dispatch(action) {
        try{
            // 执行reducer, 返回state
            state = reducer(state, action)
        }catch(e) {
            console.log('dispatch error: 'e)
        } 

        // 订阅
        listeners.forEach(listener => listener())

        // 返回action
        return 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 => {
        // 先返回一个store
        const store = createStore(reducer)

        // 创建middleApi
        const middleApi = {
            getState: store.getState,
            dispatch: (...args) => store.dispatch(...args) // 返回一个新的dispatch
        }

        // 注入middleApi
        // 并返回函数集合
        const chain = middlewares.map(middleWare => middleWare(middleApi))

        // 通过compose函数,执行所有中间件,并返回一个新的dispatch
        const 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'

    // 创建reducer
    function 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的原理也就基本能够明白了,有问题欢迎在评论中指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络之IP协议(详解

网络层主管地址管理与路由选择。而IP协议就是网络层中一个非常重要的协议。它的作用就是在复杂的网络环境中确定一个合适的路径。IP协议头格式4位版本号(version) 指定IP协议的版本&#xff0c;目前只有两个版本&#xff1a;IP v4和IP v6.对于IP v4来说&#xff0c;这个值就是4…

边缘云是什么?

涂鸦边缘云服务 旨在解决物联网边缘位置的连接需求和提高设备自主管理能力。并与涂鸦 IoT 云服务和 IoT 终端形成云边端三位一体的端到端产品架构。使用涂鸦边缘云&#xff0c;能极大降低设备响应延时、降低网络带宽压力、提高算力分发能力&#xff0c;并构建以下技术优势&…

IDEA 30 个好用天花板技巧,敲代码直接接爽到飞。

IDEA 作为Java开发工具的后起之秀&#xff0c;几乎以碾压之势把其他对手甩在了身后&#xff0c;主要原因还是归功于&#xff1a;好用&#xff1b;虽然有点重&#xff0c;但依旧瑕不掩瑜&#xff0c;内置了非常多的功能&#xff0c;大大提高了日常的开发效率&#xff0c;下面汇总…

LAMP架构与搭建论坛

目录 1、LAMP架构简述 2、各组件作用 3、构建LAMP平台 1.编译安装Apache httpd服务 2.编译安装mysql 3.编译安装php 4.搭建一个论坛 1、LAMP架构简述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整台系统和相关软件&#xff0c;能够提供动…

Spring Boot整合Thymeleaf和FreeMarker模板

虽然目前市场上多数的开发模式采用前后端分离的技术&#xff0c;视图层的技术在小一些的项目中还是非常有用的&#xff0c;所以一直也占有一席之地&#xff0c;如spring官方的spring.io等网站就是使用视图层技术实现的。 目前Spring Boot支持的较好的两个视图层模板引擎是Thyme…

【git】git版本控制

目录 1.在合适的位置打开bush,创建仓库 2.检查&#xff1a;跳转到当前文件夹&#xff0c;显示当前文件夹的相对路径 3.初始化 4.创建一个文本文件readme.txt 5.手动向readme文件中添加一些内容 6.把文件添加到暂存区 7.把文件提交到git仓库 8.手动修改readme.txt文件 9.查看当前…

前端监控之用户行为监控实践2(数据统计mongodb)

一、技术栈介绍 我们当前的项目&#xff0c;后端是node 搭建&#xff0c;数据库是非关系型数据库 mongodb。 二、数据情况介绍 日志存储存储格式如下&#xff1a; 主要包括&#xff1a; key意义type当前访问类型actionTime访问时间content访问内容erp、fullname、orgname、…

【Spring MVC】这一篇,带你从入门到进阶

目录 1、什么是MVC&#xff1f; 2、什么是 Spring MVC 3、如何学好 Spring MVC&#xff1f; 3.1、如何创建 Spring MVC 项目 3.1.1、使用Spring Initializr创建&#xff08;推荐&#xff09; 3.2、将 Spring 程序与用户&#xff08;浏览器&#xff09;联通 3.3、基础注解…

6.5 拓展:如何实现 Web API 版本控制,同时兼容无版本控制的原始接口?

第6章 构建 RESTful 服务 6.1 RESTful 简介 6.2 构建 RESTful 应用接口 6.3 使用 Swagger 生成 Web API 文档 6.4 实战&#xff1a;实现 Web API 版本控制 6.5 拓展&#xff1a;如何实现 Web API 版本控制&#xff0c;同时兼容无版本控制的原始接口&#xff1f; 6.5 拓展&#…

干旱预测方法总结及基于人工神经网络的干旱预测案例分析(MATLAB全代码)

本案例采用SPEI干旱指数&#xff0c;构建ANN和BP神经网络预测模型&#xff0c;并开展1~3个月预见期的干旱预测&#xff0c;对比分析干旱预测模型的适用性&#xff0c;为流域干旱预警和管理提供技术依据。 干旱预测 1 干旱预测方法 1.1 统计学干旱预测 根据历史降水或气温等…

【python】用plotly绘制正二十面体

文章目录顶点棱实现正二十面体plotly 的 Python 软件包是一个开源的代码库&#xff0c;它基于 plot.js&#xff0c;而后者基于 d3.js。我们实际使用的则是一个对 plotly 进行封装的库&#xff0c;名叫 cufflinks&#xff0c;能让你更方便地使用 plotly 和 Pandas 数据表协同工作…

设备树(配合LED驱动说明)

目录 一、起源 二、基本组成 三、基本语法 四、特殊节点 4.1 根节点 4.2 /memory 4.3 /chosen 4.4 /cpus 多核CPU支持 五、常用属性 5.1 phandle 5.2 地址 --------------- 重要 5.3 compatible --------------- 重要 5.4 中断 --------------- 重要 5.5 …

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

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

DNS,DNS污染劫持,DNS加密

1. DNS&#xff08;Domain Name System&#xff09;DNS&#xff08;Domain Name System&#xff09;&#xff0c; 也叫网域名称系统&#xff0c;是互联网的一项服务。它实质上是一个 域名 和 IP 相互映射的分布式数据库.DNS&#xff08;Domain Name Server&#xff0c;域名服务…

医疗保健和智慧城市服务将引领5G物联网采用

Juniper Research预测&#xff0c;到2026年&#xff0c;全球5G物联网连接将达到1.16亿&#xff0c;而2023年仅为1700万。该公司预测&#xff0c;医疗保健部门和智慧城市服务将在未来三年推动这1100%的增长&#xff0c;到2026年占5G物联网设备的60%以上。5G物联网技术的超低延迟…

配置Flutter开发环境

一、在Windows上搭建Flutter开发环境 1、去flutter官网下载其最新可用的安装包&#xff0c;下载地址&#xff1a;https://flutter.dev/docs/development/tools/sdk/releases 。 注意&#xff0c;Flutter的渠道版本一直在不断的更新&#xff0c;请以Flutter官网为准。 另外&…

自动化测试框架对比

Robot Framework&#xff08;RF&#xff09; 链接&#xff1a;http://robotframework.org/ Robot Framework&#xff08;RF&#xff09;是用于验收测试和验收测试驱动开发&#xff08;ATDD&#xff09;的自动化测试框架。 基于 Python 编写&#xff0c;但也可以在 Jython&…

Android 基础知识4-3.1 TextView(文本框)详解

一、前言 TextView就是一个显示文本标签的控件&#xff0c;就是用来显示文本。可以在代码或者 XML中设置字体&#xff0c;字体大小&#xff0c;字体颜色 &#xff0c;字体样式 &#xff08;加粗级斜体&#xff09;&#xff0c;文字截断&#xff08;比如&#xff1a;只显示10个字…

【Python数据挖掘入门】一、数据挖掘概况

一、数据挖掘概况 数据挖掘是指从大量的数据中&#xff0c;通过统计学、人工智能、机器学习等方法&#xff0c;挖掘出未知的、具有价值的信息和知识的过程。 典型案例&#xff1a; 啤酒与尿布杜蕾斯与口香糖杜蕾斯与红酒 数据挖掘是一门交叉学科&#xff0c;覆盖了统计学、数…

正则表达式常见语法_findall方法、r原串的使用

正则表达式常见语法 re.findall&#xff08;&#xff09;方法 findall&#xff08;&#xff09;方法中flag参数的作用 运行结果为 运行结果是空列表&#xff0c; 以上说明&#xff0c;正则表到时中的“点号”不能和换行符匹配。 如果匹配模式设置为re.DOTALL或者re.S&#xff…