深入理解 redux

发布于 2022-10-31 13:16:14 字数 6842 浏览 132 评论 0

为什么写这篇文章

业余时间我也算看了不少优秀开源项目的源码比如 react、redux、vuex、vue、babel、ant-design 等,但是很少系统地进行总结,学到的知识非常有限,因此我一直想写一篇完善的源码解读方面的文章。

第二个原因是最近面试的过程中,发现很多候选人对 redux 的理解很浅,甚至有错误的理解。真正理解 redux 的思想的人非常好,更不要说理解它其中的精妙设计了。

因此就有了这篇文章的诞生。

REDUX 是什么

深入理解 redux 之前,首先来看下,redux 是什么,解决了什么问题。下面是 redux 官方给出的解释:

Redux is a predictable state container for JavaScript apps.

上面的概念比较抽象,如果对 redux 不了解的人是很难理解的。一个更容易被人理解的解释(同样是 redux 官方的解释):

redux 是 flux 架构的实现,受 Elm 启发

首先科普两个名字,flux 和 Elm。

flux

下面是 facebook 官方对 flux 的解释:

Application Architecture for Building User Interfaces

更具体地说:

An application architecture for React utilizing a unidirectional data flow.

flux 是随着 react 一起推出的数据管理框架,它的核心思想是单项数据流。

一图胜千言,让我们通过图来了解下flux

通过 react 构建 view,而 react 又是数据驱动的,那么解决数据问题就解决了 view 的问题,通过 flux 架构管理数据,使得数据可预测。这样 view 也变得可预测。 非常棒~

Elm

Elm 是一门编译代码到 JavaScript 的语言,它的特点是性能强和无运行时异常。Elm 也有虚拟 DOM 的实现。

Elm 的核心理念是使用 Model 构建应用,也就是说 Model 是应用的核心。构建一个应用就是构建 Model,构建更新 Model 的方式,以及如何构建 Model 到 view 的映射。

更多关于 elm 的介绍

了解了上面的东西,你会发现其实 redux 的任务就是管理数据。redux 的数据流可以用下面的图来标示:

它精妙的设计我们在后面进行解读。

最小化实现 REDUX

其实写一个 redux 并不困难。redux 源码也就区区200行左右。里面大量使用高阶函数,闭包,函数组合等知识。让代码看起来更加简短,结构更加清晰。

我们来写一个 redux 吧!

实现

开始之前我们先看下 redux 暴漏的 api

const store = {
  state: {}, // 全局唯一的state,内部变量,通过getState()获取
  listeners: [], // listeners,用来诸如视图更新的操作
  dispatch: () => {}, // 分发action
  subscribe: () => {}, // 用来订阅state变化
  getState: () => {}, // 获取state
}

上面是 redux 中最主要的 api(其实还有一个 applyMiddleware 用来实现中间件的也非常重要,将会在 redux 的核心思想部分讲解,这里暂不讨论)

createStore

createStore 是用来初始化 redux store 的,是最重要的 api。我们来实现一下:

const createStore = (reducer, initialState) => {
  // internal variablesconst store = {};
  store.state = initialState;
  store.listeners = [];
  
  // api-subscribe
  store.subscribe = (listener) => {
    store.listeners.push(listener);
  };
  // api-dispatch
  store.dispatch = (action) => {
    store.state = reducer(store.state, action);
    store.listeners.forEach(listener => listener());
  };
  
  // api-getState
  store.getState = () => store.state;
  
  return store;
};

通过上面的20行左右的代码已经实现了 redux 的最基本功能了,是不是很惊讶?我们下面来试下。

使用

我们现在可以像使用redux一样使用了我们的 redux 了。

以下例子摘自官网,你可以把下面这段脚本加上我们上面实现的 redux,拷贝到控制台执行,看下效果。是否和 redux 官方给的结果一致。

// reducerfunctioncounter(state = 0, action) {
  switch (action.type) {
  case'INCREMENT':
    return state + 1case'DECREMENT':
    return state - 1default:
    return state
  }
}

let store = createStore(counter)

store.subscribe(() =>console.log(store.getState())
)


store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

可以看出我们已经完成了 redux 的最基本的功能了。 如果需要更新 view,就根据我们暴漏的 subscribe 去更新就好了,这也就解释了 redux 并不是专门用于 react 的,以及为什么要有 react-redux 这样的库存在。

为了方便各个阶段的人员能够看懂,我省略了 applyMiddleware 的实现,但是不要担心,我会在下面 redux 核心思想章节进行解读。

REDUX 核心思想

redux 的核心思想出了刚才提到的那些之外。个人认为还有两个东西需要特别注意。一个是 reducer, 另一个是 middlewares

reducer 和 reduce

reducer 可以说是 redux 的精髓所在。我们先来看下它。reducer 被要求是一个纯函数

  • 被要求很关键,因为 reducer 并不是定义在 redux 中的一个东西。而是用户传进来的一个方法。
  • 纯函数也很关键,reducer 应该是一个纯函数,这样 state 才可预测(这里应证了我开头提到的 Redux is a predictable state container for JavaScript apps.)。

日常工作我们也会用到 reduce 函数,它是一个高阶函数。reduce 一直是计算机领域中一个非常重要的概念。

reducer 和 reduce 名字非常像,这是巧合吗?

我们先来看下 reducer 的函数签名:

fucntion reducer(state, action) {
    const nextState = {};
    // xxxreturn nextState;
}

再看下 reduce 的函数签名

[].reduce((state, action) => {
    const nextState = {};
    // xxxreturn nextState;
}, initialState)

可以看出两个几乎完全一样。最主要区别在于 reduce 的需要一个数组,然后累计变化。reducer 则没有这样的一个数组。更确切地说,reducer 累计的 时间 上的变化,reduce 是累计 空间 上的变化。

如何理解 reducer 是累计 时间 上的变化?

我们每次通过调用 dispatch(action) 的时候,都会调用 reducer,然后将 reducer 的返回值去更新 store.state。每次 dispatch 的过程,其实就是在空间上 push(action) 的过程,类似这样:

[action1, action2, action3].reduce((state, action) => {
    const nextState = {};
    // xxxreturn nextState;
}, initialState)

因此说,reducer 其实是时间上的累计,是基于时空的操作。

middlewares

关于 middleware 的概念我们不多介绍,感兴趣可以访问这里查看更多信息。

如下可以实现一个 redux 的 middlewares:

store.dispatch = functiondispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

上述代码会在 dispatch 前后进行打印信息。类似的 middleware 我们可以写很多。比如我们还定义了另外几个相似的中间件。

我们需要将多个中间件按照一定顺序执行:

// 用reduce实现compose,很巧妙。functioncompose(...funcs) {
  if (funcs.length === 0) {
    returnarg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

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

// applyMiddleware 的源码functionapplyMiddleware(...middlewares) {
  returncreateStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () =>null;
    let chain = [];

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 将middlewares组成一个函数// 也就是说就从前到后依次执行middlewares
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

// 使用let store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(logger, dispatchAndLog)
)

上面就是 redux 关于 middleware 的源码,非常简单。通过 compose middlewares 和 dispatch,使得开发者在 dispatch 的时候能够顺序执行各个中间件。基于此出现了很多非常优秀的第三方 redux 中间价,比如 redux-dev-tool, redux-log, redux-promise 等等。这种解决问题的思想也可以应用到我们平时写代码的过程中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

不顾

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文