【源码共读】| 简易实现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中间件的作用以及原理。

相关推荐
永乐春秋29 分钟前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿31 分钟前
【前端】CSS
前端·css
ggdpzhk32 分钟前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜5 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点5 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow5 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o5 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic6 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā6 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue