【源码共读】| 简易实现redux

Redux是一个常用的跨组件状态管理库,大家都很熟悉。那么,如果让我们来实现一个类似的redux,应该如何处理呢?

需求分析

Target:实现一个简易的redux redux.js.org/introductio...

从文档中我们了解到,Redux 的核心是 reducer。reducer 中包含 state 和 action 来控制状态的变化。

  • reducer 纯函数(状态变化的规则)
  • subscribe/dispatch (订阅发布)
  • getState(获取当前状态)

我们以计数器为例,写下如下reducer(状态改变规则)

tsx 复制代码
function countReducer(state = 0, action) {
  switch (action.type) {
    case 'ADD':
      return state + 1
    case 'MINUS':
      return state - 1
    default:
      return state
  }
}

简易实现

我们可以先看看源码是怎么实现的
github.com/reduxjs/red...

HappyPath

tsx 复制代码
// createStore
export default function createStore(reducer) {
  // 存储状态
  let currentState
  // 存储监听
  let currentListeners = []

  // 获取当前的state
  function getState() {
    return currentState
  }

  // 订阅
  function subscribe(listener) {
    currentListeners.push(listener)

    // 取消订阅
    return () => {
      const index = currentListeners.indexOf(listener)
      currentListeners.splice(index, 1)
    }
  }

  // 派发action
  function dispatch(action) {
    currentState = reducer(currentState, action)
    currentListeners.forEach((listener) => listener())
  }
  // 初始化
  dispatch({ type: '@@REDUX_INIT' })

  return {
    getState,
    subscribe,
    dispatch
  }
}

页面中使用

tsx 复制代码
// Redux page
import React, { Component } from 'react'
import store from '../store/'

export default class ReduxPage extends Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate()
    })
  }
  componentWillUnmount() {
    this.unsubscribe()
  }

  add = () => {
    store.dispatch({ type: 'ADD' })
  }
  minus = () => {
    store.dispatch({ type: 'MINUS' })
  }

  render() {
    return (
      <div>
        <h3>ReduxPage</h3>
        <p>{store.getState()}</p>

        <button onClick={this.add}>ADD</button>
        <button onClick={this.minus}>minus</button>
      </div>
    )
  }
}

到目前为止,我们已经成功实现了Redux的核心功能------数据订阅和发布管理。

applyMiddleware实现

在使用过程中,我们可能需要在状态变化过程中进行拦截处理。这时候就需要用到中间件。 中间件是一个函数,在Redux的dispatch过程中可以拦截和处理action。通过使用中间件,可以在将action发送给reducer之前对其进行额外处理,例如记录日志、执行异步操作、跳转路由等。那么,怎么实现呢?

需求:

  • 有一个注册函数来注入中间件
  • 每次调用dispatch时,执行中间件

我们可以参考Koa中间件的原理,利用洋葱圈模型来依次执行中间件,可以写出如下代码:

tsx 复制代码
import compose from './compose'

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer) => {
    const store = createStore(reducer)
    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      // 保证是最新的dispatch
      dispatch: (action, ...args) => dispatch(action, ...args)
    }

    const middlewareChain = middlewares.map((middleware) => middleware(midApi))

    // 加强的dispatch
    // 所有中间件的集合,同时执行store.dispatch
    dispatch = compose(...middlewareChain)(store.dispatch)

    return {
      ...store,
      // 加强的dispatch
      dispatch
    }
  }
}
tsx 复制代码
// compose
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  )
}

这时候我们需要改造下createStore

tsx 复制代码
// createStore
export default function createStore(
  reducer,
  // 增强
  enhancer
) {
  if (enhancer) {
    return enhancer(createStore)(reducer)
  }

// 省略代码....
}

我们来测试一下,引入redux-logger

成功打印出日志,可以看出,我们已经成功实现了applyMiddle功能。

combineReducers实现

在开发过程中,可能会有多个状态(state),但需要对这些状态进行管理。这时候就可以使用combineReducer来将多个状态组合成一个整体的状态。

tsx 复制代码
export default function combineReducers(reducers) {
  // 返回一个合并后的reducer函数
  return function combination(state = {}, action) {
    // 生成新的state
    let nextState = {}
    // 标识state是否发生变化
    let hasChanged = false

    for (const key in reducers) {
      const reducer = reducers[key]
      // 之前key的state
      const previousStateForKey = state[key]
      // 执行reducer,获得新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      nextState[key] = nextStateForKey
    }

    hasChanged =
      hasChanged || Object.keys(nextState).length !== Object.keys(state).length

    return hasChanged ? nextState : state
  }
}

执行代码,可以看到当前的state已经变成了对象,而countReducerkey-value的形式存储

Plugin

redux-logger 实现

  • 打印日志
tsx 复制代码
function logger({ getState, dispatch }) {
  return (next) => (action) => {
    console.log(`--------------`)
    console.log(`action type ${action.type}`)
    console.log(`prev state`, getState())
    const returnValue = next(action)
    console.log(`next state`, getState())
    console.log(`--------------`)

    return returnValue
  }
}

redux-thunk 实现

  • 允许 action 传入函数
tsx 复制代码
function thunk({ getState, dispatch }) {
  return (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState)
    }
    return next(action)
  }
}

redux-promise 实现

  • 支持action异步
tsx 复制代码
function promise({ getState, dispatch }) {
  return (next) => (action) => {
    return isPromise(action) ? action.then(dispatch) : next(action)
  }
}

总结

通过上述代码实现,我们了解到redux是通过一个独立的存储库来存储状态,并使用发布-订阅模式来更新状态。同时,引入了洋葱模型以注册中间件,用于处理异步操作和其他副作用。

Plugin部分,我们也简单的实现了部分中间件的逻辑,通过这些实现,我们可以更深入的了解到redux中间件的作用以及原理。

相关推荐
SEO_juper7 分钟前
2026年用Python分析网站访问日志:看清Googlebot和AI爬虫怎么爬你的站(附完整代码)
开发语言·前端·seo·独立站·谷歌优化
摸鱼仙人~8 分钟前
React 原理进阶:彻底理解 re-render、React.memo、useMemo 与 useCallback
前端·vue.js
IMPYLH12 分钟前
HTML 的 <small> 元素
前端·网络·html
IT_陈寒13 分钟前
Vue的响应式让我加班到凌晨,问题竟出在这个不起眼的地方
前端·人工智能·后端
AlienZHOU9 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc12 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师13 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen14 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒14 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow15 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端