Redux 原理探秘

简介: Redux 是一个非常不错的状态管理库,和 Vuex 不同的是 Redux 并不和 React 强绑定,你甚至可以在 Vue 中使用 Redux。当初的目标是创建一个状态管理库,来提供最简化 API。

Redux 是一个非常不错的状态管理库,和 Vuex 不同的是 Redux 并不和 React 强绑定,你甚至可以在 Vue 中使用 Redux。当初的目标是创建一个状态管理库,来提供最简化 API。


复习


具体 Redux 的使用细节我们不再啰嗦,网上有很多教程,这里只使用Redux 最基本的用法。

首先实现 store

import { Action, legacy_createStore as createStore } from "redux";
const reducer = (state = 0, action: Action) => {
  switch (action.type) {
    case 'ADD':
      return state + 1
    case 'SUB':
      return state - 1
    default:
      return state
  }
}
const store = createStore(reducer)
export default store
复制代码

createStore 已经被标记为废弃,但是提供了一个 legacy_createStore 方法用于兼容之前的代码,相较之下官方更推荐使用 RTK

然后在页面中使用store,这里我们先复习一下 store 对象的几个常用 API

  • getState:获取 store 的当前状态;
  • dispatch:传递一个 Action 对象作为参数,用于修改状态;
  • subscribe:订阅,用于在状态发生改变时执行传入的回调(例如更新渲染),返回一个函数,用于取消订阅。
import store from './store'
class Count extends Component {
  unsubscribe: (() => void) | undefined;
  componentDidMount() {
    // 组件挂载时订阅 store
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate()
    })
  }
  componentWillUnmount() {
    // 卸载组件时取消订阅
    this.unsubscribe!()
  }
  add = () => {
    store.dispatch({ type: 'ADD' })
  }
  sub = () => {
    store.dispatch({ type: 'SUB' })
  }
  render() {
    return <div>
      {store.getState()}
      <button onClick={this.add}>+</button>
      <button onClick={this.sub}>-</button>
    </div>
  }
}
复制代码

现在我们的页面上就可以看到效果了,点击对应的按钮数据就会发生相应的改变

1682520769(1).png


实现一个丐版


我们基于上面的复习代码作为测试用例,我们来手动实现这几个常用的 API。首先在 src 下创建目录 /src/lib/redux,新建 index.ts 文件

export function legacy_createStore
  <S, A extends Action>(reducer: Reducer<S, A>) {
  let currentState: S;
  let listeners: Array<() => void> = [];
  function getState() {
  }
  function dispatch<T extends A>(action: T) {
  }
  function subscribe(listener: () => void) {
  }
  return {
    getState,
    dispatch,
    subscribe
  }
}
复制代码

getState 很简单,只需要把 currentState 返回即可;subscribe 需要将传入的回调函数保存到 linsteners 中,然后把移除回调的函数返回即可。

function getState() {
  return currentState
}
function subscribe(listener: () => void) {
  listeners.push(listener);
  return () => {
    const index = listeners.indexOf(listener);
    listeners.splice(index, 1);
  }
}
复制代码

在处理 dispatch 的时候,要注意 dispatch 是有返回值的,返回值是传入的 action,我们跟随 redux 一样实现即可。

1682520795(1).png

function dispatch<T extends A>(action: T) {
  currentState = reducer(currentState, action);
  listeners.forEach(listener => listener())
  return action;
}
复制代码

此时三个常用的 API 已经完成,此时将组件中引用的 redux 替换为我们刚才在本地实现的 redux,打开浏览器会看到原来显示的 0 并没有成功想显示,但是点击两个按钮对应的值却正确的显示了出来。

这是因为我们刚才的逻辑中没有给 currentState 赋初始值,我们只需要在刚才的函数中触发一次 dispatch 即可,这里传入的 action.type 写一个用户不会写的值集即可,例如当前的日期。

dispatch({ type: new Date().toUTCString() })
复制代码

此时页面实现的效果就跟我们之前用官方redux 实现的效果一样了。


增强:中间件


目前为止 redux 的 dispatch 只支持最基础的对象作为 action,如果你想使用函数作为 action,会在控制台看到报错

sub = () => {
  // @ts-expect-error
  store.dispatch(dispatch => {
    setTimeout(() => {
      dispatch({type: "SUB"});
    }, 1000);
  });
}
复制代码

1682520821(1).png

这时候 redux 会提示你是否需要为 redux 安装中间件,这里我们为 redux 安装 thunk 和 logger 两个中间件

$ npm install redux-thunk redux-logger
复制代码

由于 redux-logger 本身没有生命类型,所以在使用ts 时会抛出错误,我们需要额外为 logger 安装类型包

$ npm install @types/redux-logger -D
复制代码

安装完类型包之后我们需要在 tsconfig.json 中添加类型,否则编译时还是无法解析类型

{
  "compilerOptions": {
    "types": [
      "redux-logger"
    ]
  },
}
复制代码

此时我们就可以为 redux 添加中间件了

import { Action, applyMiddleware } from "redux";
import { legacy_createStore as createStore } from "redux";
import thunk from "redux-thunk";
import logger from 'redux-logger';
const reducer = (state = 0, action: Action) => {
  switch (action.type) {
    case 'ADD':
      return state + 1
    case 'SUB':
      return state - 1
    default:
      return state
  }
}
const store = createStore(reducer, applyMiddleware(thunk, logger))
export default store
复制代码

此时将我们在页面中点击按钮,不会再抛出异常

1682520849(1).png

在了解了中间件的使用方法之后,我们也给我们的丐版 redux 添加中间件机制。

上一小节的 Reducer 其实是函数式编程概念中的纯函数,函数式编程中还有另一个概念——柯里化,这里我们就不再强调,不了解的可以百度。

先来看和一个示例

function f1(arg: any) {
  console.log('f1', arg);
  return arg;
}
function f2(arg: any) {
  console.log('f2', arg);
  return arg;
}
function f3(arg: any) {
  console.log('f3', arg);
  return arg;
}
复制代码

我们想让f1函数的返回值作为f2的参数,再让 f2 的返回值作为 f3的参数,按照最简单的方法,我们可以嵌套调用

f3(f2(f1('function')))
复制代码

这样虽然可以实现我们要的效果,但是如果函数多了的时候就很不方便,我们可以利用柯里化,让函数自动完成嵌套

const compose = (...fns: Function[]) => {
  if (fns.length === 0) {
    return (arg: any) => arg;
  }
  if (fns.length === 1) {
    return fns[0];
  }
  return fns.reduce(
    (fn1, fn2) =>
      (...args: any[]) =>
        fn1(fn2(...args)),
  );
};
compose(f1, f2, f3)('function')
// f3 function
// f2 function
// f1 function
复制代码

我们利用数组的 reduce 方法,将函数依次进行嵌套,这其实就是 redux 中 middleware 的实现方式。

现在有没有感觉很眼熟,我们刚才引入的 thunk 和 logger其实就是一个函数,我们引入中间件的方式就跟这里调用 compose 类似。

我们来自己实现一个 applyMiddleware,我们需要改写原有的 dispatch,让其可以通过 middleware 来增强功能。

export function applyMiddleware(...middlewares: Middleware[]) {
  return (createStore: typeof legacy_createStore) => (reducer: Reducer) => {
    const store = createStore(reducer)
    let dispatch = store.dispatch
    const midAPI = {
      getState: store.getState,
      dispatch: (action: Action, ...args: any[]) => dispatch(action, ...args)
    }
    // 给 middleware 传入新的 dispatch
    const middlewareChain = middlewares
      .map(middleware => middleware(midAPI))
    // 增强 dispatch(把所有中间件函数都执行)
    dispatch = compose(...middlewareChain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
};
复制代码

这时替换掉import 的位置,回到页面可以看到效果和刚才一模一样。

相关文章
|
存储 缓存 JavaScript
深入浅出 RxJS 核心原理(响应式编程篇)
在最近的项目中,我们面临了一个需求:监听异步数据的更新,并及时通知相关的组件模块进行相应的处理。传统的事件监听和回调函数方式可能无法满足我们的需求,因此决定采用响应式编程的方法来解决这个问题。在实现过程中发现 RxJS 这个响应式编程库,可以很高效、可维护地实现数据的监听和组件通知。
353 0
深入浅出 RxJS 核心原理(响应式编程篇)
|
16天前
|
存储 JavaScript 前端开发
Redux原理
【10月更文挑战第26天】Redux通过单一数据源、只读状态、纯函数修改状态等核心概念,以及清晰的工作流程和中间件机制,为JavaScript应用程序提供了一种强大而可预测的状态管理方案。它与React等视图库的结合能够有效地实现数据驱动的视图更新,提高应用程序的可维护性和可扩展性。在实际应用中,根据项目的具体需求和复杂度,可以灵活地运用Redux及其相关的工具和技术,来构建高效、稳定的前端应用。
70 33
|
2月前
|
存储 移动开发 前端开发
探秘react,一文弄懂react的基本使用和高级特性
该文章全面介绍了React的基本使用方法与高级特性,包括JSX语法、组件化设计、状态管理、生命周期方法、Hooks使用、性能优化策略等内容,并探讨了Redux和React Router在项目中的集成与应用。
探秘react,一文弄懂react的基本使用和高级特性
|
3月前
|
缓存 JavaScript 前端开发
【React生态进阶】React与Redux完美结合:从原理到实践全面解析构建大规模应用的最佳策略与技巧分享!
【8月更文挑战第31天】React 与 Redux 的结合解决了复杂状态管理的问题,提升了应用性能。本文详细介绍了在 React 应用中引入 Redux 的原因、步骤及最佳实践,包括安装配置、状态管理、性能优化等多方面内容,并提供了代码示例,帮助你构建高性能、易维护的大规模应用。
57 0
|
JavaScript 中间件 API
redux原理是什么
redux原理是什么
92 0
|
监控 JavaScript 前端开发
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-redux中间件机制是什么
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-redux中间件机制是什么
73 0
|
JavaScript 前端开发 中间件
redux的实现原理是什么,核心代码?
redux的实现原理是什么,核心代码?
|
存储 JSON JavaScript
「前端架构」Redux vs.MobX的权威指南
「前端架构」Redux vs.MobX的权威指南
|
监控 JavaScript 前端开发
React-Redux 100行代码简易版探究原理
各位使用 react 技术栈的小伙伴都不可避免的接触过redux + react-redux的这套组合,众所周知 redux 是一个非常精简的库,它和 react 是没有做任何结合的,甚至可以在 vue 项目中使用。