造一个 redux 轮子(上)

简介: Redux 应该是很多前端新手的噩梦。还记得我刚接触 Redux 的时候也是刚从 Vue 转过来的时候,觉得Redux 概念非常多,想写一个 Hello World 都难。文档也是很难看懂,并不是看不懂英文,而是看的时候总会想:TMD在说泥🐴呢。看得出文档想手把手把新手教好,结果却是适得而反,啰嗦的排版和系统性地阐述让新手越来越蒙逼。文档还有一步令人窒息的操作:把 redux、react-redux、redux-toolkit 三个库放在一起来讲。靠,你的标题叫 redux 文档啊,就讲 Redux 不就行了嘛?搞得新手总会觉得 Redux 就是像 Vuex 一样为 React 量身订做的。

image.png章源码:github.com/Haixiang612…

参考轮子:www.npmjs.com/package/red…


前言吐槽


Redux 应该是很多前端新手的噩梦。还记得我刚接触 Redux 的时候也是刚从 Vue 转过来的时候,觉得Redux 概念非常多,想写一个 Hello World 都难。


文档也是很难看懂,并不是看不懂英文,而是看的时候总会想:TMD在说泥🐴呢。看得出文档想手把手把新手教好,结果却是适得而反,啰嗦的排版和系统性地阐述让新手越来越蒙逼。文档还有一步令人窒息的操作:把 redux、react-redux、redux-toolkit 三个库放在一起来讲。靠,你的标题叫 redux 文档啊,就讲 Redux 不就行了嘛?搞得新手总会觉得 Redux 就是像 Vuex 一样为 React 量身订做的,其实并不是。


Redux 和 React 的关系


Redux 和 React 根本没关系。

看 Redux 的官网开头:"A Predictable State Container for JS Apps"。再看 Vuex 的官网开头:"Vuex is a state management pattern + library for Vue.js applications"


请问哪里出现了 "react" 这个单词了?


两者的定位本来就不一样:Redux 仅仅是个事件中心(事件总线,随便怎么叫),就是 for JS Apps 的。而 Vuex 除了事件中心,也是 for Vue.js applications 的。


解决了什么问题


为了重新认识 Redux,我们先搞清楚 Redux 到底是个啥、解决了什么问题。

简单来说:


  • 创建一个事件中心,里面存一些数据,叫 store
  • 向外提供读、写操作,叫 getStatedispatch,通过分发事件修改数据,叫 dispatch(action)
  • 添加监听器,每次 dispatch 数据改了,就触发监听器,达到监听数据变化的效果,叫 subscribe


image.png


Redux 本来就是一个超级简单的库,只是文档不知不觉把它写复杂了,搞得新手无从下手,口口相传觉得 Redux 很难、很复杂。其实 Redux 一点都不难、简单得一批。

不信?下面就带大家一起写一个完整的 Redux。


createStore


这个函数创建一个 Object,里面存放数据,并提供读和写方法。实现如下:

function createStore(reduce, preloadedState, enhancer) {
  let currentState = preloadedState // 当前数据(状态)
  let currentReducer = reducer // 计算新数据(状态)
  let isDispatching = false // 是否在 dispatch
  // 获取 state
  function getState() {
    if (isDispatching) {
      throw new Error('还在 dispatching 呢,获取不了 state 啊')
    }
    return currentState
  }
  // 分发 action 的函数
  function dispatch(action) {
    if (isDispatching) {
      throw new Error('还在 dispatching 呢,dispatch 不了啊')
    }
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    return action
  }
  return {
    getState,
    dispatch
  }
}
复制代码


上面将数据存于 currentStategetState 返回当前数据。在 dispatch 里使用 reducer 计算新的数据(状态)从而修改 currentState


上面还用 isDispatching 防止多重 dispatch 情况下操作同一资源的问题。


假如别人不给你传 preloadedState,那 currentState 初始时就会为 undefuned 了呀,undefined 作为 state 是不行的。为了解决这个问题,可以在 createStore 的时候直接 dispatch 一个 action,这个 action 不命中所有 reducer 里的 case,那么 reducer 都返回初始值,以此达到初始化 state 的目的,这也是为什么在 reducer 里的 switch-case 的 default 一定要返回 state 而不是啥都不处理。

// 生成随机字符串,注意这里的 toString(36) 的 36 是基数
const randomString = () => Math.random().toString(36).substring(7).split('').join('.')
const actionTypes = {
  INIT: `@@redux/INIT${randomString()}`, // 为了重名,追加随机字符串
}
function createStore(reduce, preloadedState, enhancer) {
  ...
  // 获取 state
  function getState() {
    ...
  }
  // 分发 action 的函数
  function dispatch(action) {
    ...
  }
  // 初始化
  dispatch({type: actionTypes.INIT})
  return {
    getState,
    dispatch
  }
}
复制代码


然后就可以用我们的 Redux 啦~

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return state + action.payload
    case 'decrement':
      return state - action.payload
    default:
      return state
  }
}
const store = createStore(reducer, 1) // 1,不管有没有初始值,都会 dispatch @@redux/INIT 来初始化 state
store.dispatch({ type: 'increment', payload: 2 }) // 1 + 2
console.log(store.getState()) // 3
复制代码


isPlainObject 和 kindOf


Redux 对 action 是有要求的,一定要是普通对象。所以我们还要需要判断一下,如果不是普通对象,就抛出错误并说明 action 此时的类型。

// 分发 action 的函数
function dispatch(action: A) {
  if (!isPlainObject(action)) { // 是不是纯对象
    throw new Error(`不是纯净的 Object,是一个类似 ${kindOf(action)} 的东西`) // 不是,是一个类似 XXX 的东西
  }
  ...
}
复制代码


这里的 isPlainObjectkindOf 都是可以从 npm 里的 is-plain-objectkind-of 获得。这两个包实现都很简单。是不是会觉得:啊?就这?就这么小的包都有几万的下载量???我自己实现也行啊。没错,前端开发就是这么无聊,写这么小的包都能一炮而红,只难当年还不会 JS 没能夺得先机 😢。


这里我们用 npm  包,自己实现一波吧:


首先是 isPlainObject,一般来说通过判断 typeof obj === 'object' 就可以了,但是 typeof  null 也是 object,这是因为最初实现 JS 的时候,用 typevalue 表示 JS 的值,当 type === 0 时表示是 Object,而当初 null 的地址又为 0x00 所以 null 的 type 一直是 0,因此 typeof null === null,可以 参考这里。 另一个点是原型键只有一层。


const isPlainObject = (obj: any) => {
  // 检查类型
  if (typeof obj !== 'object' || obj === null) return false
  // 检查是否由 constructor 生成
  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  return Object.getPrototypeOf(obj) === proto
}
export default isPlainObject
复制代码


另一个函数 kindOf 实现就繁琐多了,除了要判断一些简单的 typeof 值,还要判断 Array, Date, Error 等多种对象。

const isDate = (value: any) => { // 是不是 Date
  if (value instanceof Date) return true
  return (
    typeof value.toDateString === 'function' &&
    typeof value.getDate === 'function' &&
    typeof value.setDate === 'function'
  )
}
const isError = (value: any) => { // 是不是 Error
  if (value instanceof Error) return true
  return (
    typeof value.message === 'string' &&
    value.constructor &&
    typeof value.constructor.stackTraceLimit === 'number'
  )
}
const getCtorName = (value: any): string | null => { // 获取
  return typeof value.constructor === 'function' ? value.constructor.name : null
}
const kindOf = (value: any): string => {
  if (value === void 0) return 'undefined'
  if (value === null) return 'null'
  const type = typeof value
  switch (type) { // 有字面意思的值
    case 'boolean':
    case 'string':
    case 'number':
    case 'symbol':
    case 'function':
      return type
  }
  if (Array.isArray(value)) return 'array' //是不是数组
  if (isDate(value)) return 'date' // 是不是 Date
  if (isError(value)) return 'error' // 是不是 Error
  const ctorName = getCtorName(value)
  switch (ctorName) { // 构造函数中读取类型
    case 'Symbol':
    case 'Promise':
    case 'WeakMap':
    case 'WeakSet':
    case 'Map':
    case 'Set':
      return ctorName
  }
  return type
}
复制代码

上面两个函数在学习 Redux 并不是很重要,不过可以我们提供实现这两个工具函数的一些灵感,下次再次使用时我们也可以直接手写出来。


replaceReducer


replaceReducer 这个函数别说用了,估计没多少人听说过。在 Code Spliting 的时候才会用到。比如打包出来有 2 个 JS,第一个先加载了 reducer,第二个加载新的 reducer,这里可以用 combineReducers 去完成合并。

const newRootReducer = combineReducers({
  existingSlice: existingSliceReducer,
  newSlice: newSliceReducer
})
store.replaceReducer(newRootReducer)
复制代码


现在有太多做动态模块、代码分割的库帮我们做了这些事情了,所以我们没多大机会用到这个 API。

实现上也很简单,就是把原来的 reducer 替换掉就可以了。

const actionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`
}
function createStore(reducer, preloadedState, enhancer) {
  ...
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer
    dispatch({type: actionTypes.REPLACE} as A) // 重新初始化状态
    return store
  }
  ...
}
复制代码

上面除了直接替换,还 dispatch 了 @@redux/REPALCE 这个 action。把当前状态都重置了。

相关文章
|
4天前
|
云安全 人工智能 运维
阿里云SecOps Agent,全新安全跨产品执行体验
自然语言驱动 云安全中心/WAF/CFW/ 等多款安全产品联动
1597 2
|
2天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
357 123
|
4天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
607 4
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
15天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
16天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
929 12
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
8天前
|
缓存 人工智能 运维
GLM 5.2自托管全流程实战:硬件选型、vLLM/SGLang部署与成本盈亏测算
2026年智谱发布GLM 5.2超大混合专家模型,区别于以往仅开放API的闭源大模型,该模型权重以MIT开源协议对外发布,企业与开发者可完整下载、本地审计、私有化部署,实现数据不出环境、自定义微调、自主调度推理资源。GLM 5.2拥有753B总参数,原生支持百万级上下文窗口,在代码生成、长文档推理、数学逻辑等多项基准测试中对标国际顶尖商用模型,是首款可完整自托管的前沿代码向大模型。
693 0
|
3天前
|
消息中间件 人工智能 Kafka
AI 时代,实时入湖正在告别 ETL:从 Kafka 到 Iceberg 的架构减法
本文围绕“零 ETL”这一趋势,讨论流数据入湖为什么需要做架构减法,并结合 Kafka × Table Bucket 的实践,分析一种将通用入湖能力前移到消息与表存储链路中的方案,如何在降低复杂度的同时,兼顾实时性、一致性、Schema 演进、CDC 语义与开放生态兼容。
195 121
|
3天前
|
人工智能 监控 前端开发
Electron 监控:让桌面 Agent 监控触手可及
一行代码实现Electron桌面端全景监控,自动还原崩溃现场、预警内存泄漏、全链路追踪、 SSE流式响应与交互埋点,让 AI 助手运行状态清晰可见,助力快速恢复稳定与流畅。
187 125
|
11天前
|
人工智能 自然语言处理 算法
阿里云百炼Qwen 3.7 Plus与Max实测全解:性价比与多模态能力、成本深度对比
2026年,阿里云百炼平台推出的Qwen 3.7系列成为企业与开发者落地AI应用的核心选择,其中Qwen 3.7 Max与Plus作为两大旗舰版本,定位差异显著:Max是纯文本推理旗舰,专注高强度智能体与复杂逻辑任务;Plus则是多模态全能版,在保留强大文本能力的同时,补齐图像、视频理解能力,且价格大幅降低。本文基于2026年最新实测数据,从核心参数、文本能力、多模态能力、智能体表现、性价比与场景选型六大维度,全面解析两款模型的差异,为用户提供精准选型参考。
555 0