深度理解Redux原理并实现一个redux

news2025/1/16 12:56:01

Redux的作用是什么

Redux的作用在于实现状态传递状态管理。在这里你可能会说了,如果是状态传递,那我props的传递不也是可以达到这样的效果吗?context上下文方案不也是可以达到这样的效果吗?没错,是这样的,但是上述的两种方案是有局限性的。

  • props方案只适用于父子组件传递状态。
  • context上下文方案虽然能够在根组件上定义上下文,但是有两种缺陷
    • 只要上下文里面的状态发生改变,就会重新渲染整个组件树,进而会产生庞大的性能开销。
    • 组件的逻辑与状态的耦合度太高,不利于解耦,也就是无法实现对状态的统一管理。

既然Redux的作用是对状态的管理与传递,那么他的作用场景呢?当然了你可以根据上面说的两种方案对Redux的使用做取舍,Redux的本质就是全局变量被协调管理。

  • 如果涉及多个状态,且多个状态会被多个组件使用到,比如商城购物车场景,你就可以毫不犹豫的考虑用Redux
  • 如果涉及多个状态,但是状态虽多但是是用的组件唯一,或者有关联关系的组件使用,你就大可不必使用Redux,如果状态不是那么多,那就更不必使用Redux了。

除此之外,Redux还有一个优点就是,不仅仅是React本身能够使用,就连别的框架,比如jQuerykerry_domvue等都可以使用,但是对比于vue来讲的话,vue有自己比较好的的状态管理库vuex,好了废话不多说了,我们先来看看Redux在项目中是如何是用的。

Redux的使用

// store.js
import { createStore } from "redux";
import reducer from "./reducer";

export default createStore(reducer);


// reducer.js
import {cloneDeep} from 'lodash';
const initilaValue = {
    count: 0
};

const reducer = (state = initilaValue, action) => {
    state = cloneDeep(state)
    const { type, payload } = action;
    switch (type) {
      case "add":
        state.count += payload;
        break;
      case "reduce":
        state.count -= payload;
        break
      default:
    }
  return state;
};

export default reducer;

// App.js
import React, {Component} from 'react';
import store from "./store";

export default class App extends Component {
    componentDidMount() {
      //reducer不会触发页面变化,需要state来触发
      store.subscribe(() =>{
        this.setState(store.getState())
      })
    }

    render() {
    //获取reducer数据
    const {count} = store.getState()
      return (
        <div>
          <div type='primary' onClick={this.reduce}>-</div>
          <span>{count}</span>
          <div type='primary' onClick={this.add}>+</div>
        </div>
      );
    }
    reduce = () => {
      //通知reducer页面数据变化了
      store.dispatch({
        type: 'reduce',
        payload: 1
      })
    }
    add = () => {
      //通知reducer页面数据变化了
      store.dispatch({
        type: 'add',
        payload: 1
      })
    }
}

上述代码就可以实现count的加减计算了,我们可以看到有几处需要注意的地方。

  • store.js文件里面的createStore
  • reducer.js文件里面的cloneDeepreturn statestate = initialValue
  • App.js文件里面的dispatchgetStatetypepayload

很明显createStore的作用就是创建仓库,getState为取得当前的state值,dispatch为某个操作之后派发给store去更新某个statetype为具体的某种交互,payload为每次交互的具体内容。各位同学可以看得到我在reducer中做了一次state的深克隆,这是为什么呢?是因为在每一次的action中我们拿到的是同一个state内存地址,我们的期望是不管你在switch中如何更改state但是我不希望在这一步就改变了公共状态中的count,只有在我return的时候才会去更改真正的公共状态,也就是说reducer函数执行产生的私有闭包里面的公共状态信息。而state = initialValue这一步的操作就是第一次派发的时候,reducer接收的state为空,我们把基础值赋给它。了解了这么多,我们还是去看一下他的源码是如何实现的吧。

Redux的源码

//Redux/redux/src/index.ts
export {
  createStore, // 创建仓库api
  combineReducers, // 合并Reducer
  bindActionCreators, // 转化action
  applyMiddleware, // 中间件应用方案
  compose,// 函数组合
  __DO_NOT_USE__ActionTypes
}

上面的index.ts文件夹里面暴露出了几个api,我们主要针对createStore看看。

export default function createStore(
  reducer: Reducer<S, A>,  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
  // createStore函数不支持第二三四参数为函数
  // 详见https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      ...
    )
  }

  // 第二参数是函数,且第三参数不传
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }

  // 有第三参数且不是函数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(          enhancer        )}'`
      )
    }

    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  // 如果reducer不是函数,要报错
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(        reducer      )}'`
    )
  }

  let currentReducer = reducer // 当前的reducer
  let currentState = preloadedState as S // 当前的state
  let currentListeners: (() => void)[] | null = [] // 事件池
  let nextListeners = currentListeners
  let isDispatching = false // 正在派发

  /**   * This makes a shallow copy of currentListeners so we can use   * nextListeners as a temporary list while dispatching.   *   * This prevents any bugs around consumers calling   * subscribe/unsubscribe in the middle of a dispatch.   */ 
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 返回当前的state
  function getState(): S {
    if (isDispatching) {
      throw new Error(
        ...
      )
    }
    return currentState as S
  }

  //向事件池中添加更新事件
  function subscribe(listener: () => void) {
    // 校验是不是函数
    if (typeof listener !== 'function') {
      throw new Error(
       ...
      )
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api/store#subscribelistener for more details.'
      )
    }

    let isSubscribed = true
    //避免重复添加
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    //subscribe函数每执行一次,都会返回一个unsubscribe用来从事件池中移除当前事件
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          ...
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      // 移除当前事件
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

  // 派发函数
  function dispatch(action: A) {
    // 如果传过来的action不是对象报错
    if (!isPlainObject(action)) {
      throw new Error(
       ...
      )
    }

    //每一个action中都需要一个type字段,没有就报错 
    if (typeof action.type === 'undefined') {
      throw new Error(
       ...
      )
    }

    //正在派发中..
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 执行reducer,改变state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // dispatch通知事件池去执行事件,遍历执行
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替换replaceReducer
  function replaceReducer<NewState, NewActions extends A>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {

    // 入参不是函数,报错
    if (typeof nextReducer !== 'function') {
      throw new Error(
       ...
      )
    }

    ...
    // 刚开始进来要派发一次,同步state,其中{ type: ActionTypes.REPLACE }为唯一标识
    // 倘若不为唯一标识的话,那可能一开始就破坏了状态value
    dispatch({ type: ActionTypes.REPLACE } as A)
    // change the type of the store by casting it to the new store
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }

  ...

  // 初始化store的时候,需要派发一次同步state
  dispatch({ type: ActionTypes.INIT } as A)

  const store = {
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext

  // 返回仓库  const store = createStore({count:0})
  return store
}

确实短短几百行代码实现了redux,为此我们也来实现一个简易版的redux表示敬意,我们的redux只实现getStatedispatchcreateStore方法。

// myRedux
import {cloneDeep} from 'lodash'
export function createStore(reducer) {
  if(typeof reducer !== 'function') {
    throw new Error('reducer must be an function')
  }

  let state,
      listeners = [];

  const getState = () => {
    // 深克隆一个state
    return cloneDeep(state);
  }

  const subscribe  = (listener) => {
    if(typeof listener !== 'function'){
      throw new Error('listener must be an function')
    }

    //去重
    if(!listeners.includes(listener)){
      listeners.push(listener)
    }
    // 源码里面执行subscribe,返回一个卸载函数
    return function unsubscribe(){
      listeners.filter(action => action!== listener)
    }
  }


  const dispatch = (action) => {
    if(typeof action !== 'object' && action !== null){
      throw new Error('action must be a object')
    }

    // 判断有没有type
    if(typeof action.type === undefined){
      throw new Error('action.type must existence')
    }

    //执行 
    try {
      state = reducer(state, action)
    } catch (error) {
      //...
    }

    // 通知事件池中的方法执行
    listeners.forEach(listener=>{
      if(typeof listener === 'function'){
        listener();
      }
    })
  }

  //第一次进来要派发一次,同步初始状态
  dispatch({
    type:typeof Symbol !== undefined ? Symbol('ABC') : '唯一值'
  })

  // 暴露出方法
  return {
    getState,
    dispatch,
    subscribe
  }
}

参考 前端进阶面试题详细解答

各位可以去codeSandBox上面去实践一下,当然了这只是简易版的redux,官方推荐使用react-redux来进行实际的项目开发,因为他只关注于数据管理。

总结

redux的大致工作流如此:

在这里插入图片描述

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

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

相关文章

汇川SV660N与基恩士 KV7500 控制器调试说明

1. 伺服相关部分配置 1.1 伺服相关版本 SV660N 试机建议使用“SV660N-Ecat_v0.09.xml”及以上设备描述文件。 SV660N 单板软件版本建议为“H0100901.4”及更高版本号。 1.2 相关参数说明 SV660N 对象字典中 60FD 的含义较 IS620N 有所更改&#xff1a;bit0、1、2 分别为负限位…

移动字母--降维与DFS

一、题目描述 2x3=6 个方格中放入 ABCDE 五个字母,右下角的那个格空着。如下图所示。 和空格子相邻的格子中的字母可以移动到空格中,比如,图中的 C 和 E 就可以移动,移动后的局面分别是: A B D E C A B C D E 为了表示方便,我们把 6 个格子中字母配置用一个串表示出…

如何创建出实用的员工手册?

员工手册主要是企业内部的人事制度管理规范&#xff0c;包含企业规章制度和企业文化&#xff0c;同时还起到了展示企业形象、传播企业文化的作用。它既覆盖了企业人力资源管理的各个方面规章制度的主要内容&#xff0c;又因适应企业独特个性的经营发展需要而弥补了规章制度制定…

【HTML】列表结构

列表结构HTML效果HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdev…

HIVE --- zeppelin安装

目录 把zeppelin压缩包拷贝到虚拟机里面 解压 改名 修改配置文件 编辑zeppelin-site.xml—将配置文件的ip地址和端口号进行修改 编辑 zeppelin-env.sh—添加JDK和Hadoop环境 配置环境变量 刷新环境变量 拷贝Hive文件 拷贝外部文件 启动zeppelin 启动Hadoop&Hi…

Web API接口鉴权方式

一、什么是鉴权&#xff1f;为什么要鉴权 鉴权&#xff08;authentication&#xff09;&#xff0c;也叫做认证&#xff0c;即验证用户是否拥有访问系统的权利。 HTTP本身是无状态的请求&#xff0c;每次请求都是一次性的&#xff0c;并不会知道请求前后发生了什么。但在很多…

记一次linux服务器磁盘空间占满的问题排查

问题&#xff1a;服务器安装后两天&#xff0c;发现磁盘空间使用满了【date: write error: No space left on device】问题排查&#xff1a;1、使用df -hl命令查看2、使用du -hl --max-depth1&#xff0c;从根目录开始查起&#xff0c;最后发现&#xff0c;磁盘的空间全部被/va…

自学5个月Java找到了9K的工作,我的方式值得大家借鉴 第二部分

我的学习心得&#xff0c;我认为能不能自学成功的要素有两点。 第一点就是自身的问题&#xff0c;虽然想要转行学习Java的人很多&#xff0c;但是非常强烈的想要转行学好的人是小部分。而大部分人只是抱着试试的心态来学习Java&#xff0c;这是完全不可能的。所以能不能学成Jav…

【Linux】项目的自动化构建-make/makefile

&#x1f4a3;1.背景会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的 规则来指定&#xff0c;哪些文件需要先编译&#xff…

Java List系列(ArrayList、LinekdList 以及遍历中删除重复元素时发生的异常和解决办法)

目录List集合系列List系列集合特点List集合特有方法List集合的遍历方式ArrayList集合的底层原理分析源码LinkedList集合的底层原理集合的并发修改异常问题&#xff08;删除重复元素时&#xff09;List集合系列 List系列集合特点 ArrayList、LinekdList &#xff1a;有序&#…

HNU工训中心:电子开关与信号隔离

工训中心的牛马实验 1.实验目的&#xff1a; 1) 认识三极管和MOS管构成三端电子开关电路&#xff1b; 认识信号隔离的继电器和光电隔离方式。 2) 认识施密特触发器&#xff0c;掌握一种波形变换方法。 3) 实现一种脉冲波形发生器。 2.实验资源 HBE硬件基础电路实验箱、示波…

2.FFmpeg5.1下载和使用

1.FFmpeg库下载 进入http://ffmpeg.org/download.html 官网,如下图所示: 由于我们初期只在windows上

北京筑龙吴英礼:ChatGPT对采购与招标数字化的影响

2月25日下午&#xff0c;平台经济学沙龙&#xff08;第八期&#xff09;在清华大学互联网产业研究院成功举办。本期沙龙以“ChatGPT对招标采购的影响”为主题&#xff0c;由清华大学互联网产业研究院平台经济课题组组长、中国招标投标公共服务平台原总经理、首席经济学家平庆忠…

Leetcode19. 删除链表的倒数第n个结点

一、题目描述&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1输出&#x…

Android开发八股文,Android也有自己的八股文了

前言别的行业都有自己的八股文&#xff0c;凭什么Android没有。2023春招即将来临&#xff0c;很多同学会问 Android开发的面试题有必要背吗&#xff1f;我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。国内…

SSL/TLS协议信息泄露漏洞(CVE-2016-2183)调查解决

目录结构前言测试回馈漏洞介绍漏洞解决参考文献前言 产品测试阶段出现“SSL/TLS协议信息泄露漏洞&#xff08;CVE-2016-2183&#xff09;”&#xff0c;解决过程记录如下 测试回馈 建议&#xff1a;避免使用IDEA、DES和3DES算法 1、OpenSSL Security Advisory [22 Sep 2016] …

P02 滴水逆向1月4号公开课

公开课进制转换汇编如何工作的逻辑运算二进制逻辑运算|| 运算 和 | 运算&& 运算 和 & 运算^(异或) 运算! (非)运算左移二进制逻辑运算应用加法运算运算与电脑硬件进行加密寄存器内存寻址范围使用内存寻址公式堆栈变形的艺术进制转换 二进制十六进制如何转换的 汇…

k8s学习之路 | Day17 k8s 工作负载

文章目录工作负载的定义工作负载资源分类工作负载的定义 官方参考链接&#xff1a;https://kubernetes.io/docs/concepts/workloads/ A workload is an application running on Kubernetes. Whether your workload is a single component or several that work together, on K…

已解决ERROR: Failed building wheel for opencv-python-headless

已解决ERROR: Failed building wheel for opencv-python-headless Failed to build opencv-python-headless ERROR: Could not build wheels for opencv-python-headless, which is required to install pyproject.toml-based projects报错信息亲测有效 文章目录报错问题报错翻…

多任务学习概述

文章目录前言1 文章信息2 背景、目的、结论2.1 背景2.1.1 多任务的类型分类2.1.1.1 相关任务的分类2.1.1.2 将输入变输出的逆多任务学习2.1.1.3 对抗性多任务学习2.1.1.4 辅助任务提供注意力特征的多任务学习2.1.1.5 附加预测性辅助任务的多任务学习3 内容与讨论3.1 多任务学习…