我所理解的前端状态管理

hello,好久没更,今天我们聊聊前端状态管理史~

前言

我们知道,在前端发展初期,html网页只是静态的,任何小的改动意味着一个新的页面;之后出现了iframe和XMLHttpRequest,实现了异步的局部加载,极大的提升了用户体验;再到后面的jQuery,利用「命令式」的编程管理dom的状态,但应用一旦复杂的话也难以维护;直到近些年从Angular的诞生,到后来的React,Vue陆续推出,各类前端技术框架层出不穷,除了Vue用户熟悉的Vuex,React与Redux,还出现了Mobx,Rxjs等其他优秀的数据存储和管理的方案,这才真正有了前端状态管理的概念。

不得不说,SPA的出现进一步带动了前后端分离,前端再也不需要通过请求URL返回的HTML来显示页面,而是通过加载解析JS代码生成DOM,独立地实现了内容的渲染,在这过程中,「数据驱动」的思想也深入人心。

所谓数据驱动,是指视图是由数据驱动生成的,我们可以通过修改数据来处理DOM,这也是典型的「声明式」编程。以React为例,我们可以轻松的通过改变state更新UI,但是有一个问题,React并不会对数据层有任何的限制,即任何组件都可以改变数据层的代码,那就带来了一个问题,一旦数据层出现特殊状况,很难快速定位和解决问题,那么如何在视图组件中管理公共的状态呢?

这个时候Facebook团队提出了Flux思想,旨在从架构层面来解决MVC的在复杂场景下越来越复杂内部逻辑繁重等问题。那到底啥才是Flux架构呢?

Flux思想及其实现

Vuex官方文档里一句话很有意思:"Flux 架构就像眼镜:您自会知道什么时候需要它。"

好吧,还是不清楚,那我们先了解一下Flux的一些基本概念。(以Facebook官方实现为例)

Flux将一个应用分成四个部分,其中:「视图层 View、视图层发出的消息 Action、用来接收Actions并执行回调函数 Dispatcher、以及用来存放应用的状态的 Store」

从上图可以很明显看到,这是一个「单向流动」的过程:用户只能触发Action来修改状态,应用的状态也必须放在Store里统一管理,通过监听Action进行一些具体的操作。任何状态的变更都离不开Action的发起以及Dispatcher的派发,这样一来,我们可以很容易的记录每一次的状态变化。这也是Flux基本的设计理念,之后在此基础上也出现了越来越多的Flux实践,接下来,我们以React的状态管理发展历程为例,看看Redux是怎么管理状态的。

(对Flux感兴趣的话可以继续看阮一峰这篇 Flux架构入门教程。)

Redux

React刚出现的时候还没有Redux,我们只能通过state来管理组件的状态,这在多数情况下已经足够了,但是应用一旦复杂起来,状态还是会变得难以维护。之后又受到Flux的影响,在这种环境下,Redux应运而生。

首先,Redux有三大原则:「单一数据源,state只读,使用纯函数来执行修改」。前两条很好理解,至于第三条,要知道React遵循的是数据的不可变性,即永远不在原对象上修改属性,并且在源码中,Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同,这就意味着不能直接修改state的属性,每次只能返回新的 state。于是我们可以把Redux当作是 Reduce + Flux,而Reduce就是上面说的纯函数。

不仅如此,Redux中还有一个重要的概念 Middleware。

Redux中的中间件提供的是位于 Action 被发起之后,到达 Reducer 之前的扩展点,一图概括:

可以看到,使用中间件时,中间件会将发起的Actions做相应的处理,最后交给Reducer执行。假设我们需要在每次触发Action前打印log,那我们就可以将Dispatch方法拿出来重写,大概像这样:

let next = store.dispatch
store.dispatch = function dispatchLog(action) {  
  console.log('log', action) 
  next(action) 
}

这样以后发出Action时,就不需要做额外的工作了。

那为什么要将中间件设计成这种 middleware =(store) => (next) => (action) => { [return next(action)]} 多层柯里化的写法呢,源码中有applyMiddleware方法用来添加中间件,其核心代码大概是这样:

let store = createStore(reducers);
let dispatch;
// 这里第一次执行返回了一个存放可以修改dispatch函数的数组chain [f(next)=>acticon=>next(action)]
// 这个函数接收next参数 就是在中间件间传递的diapatch方法
let chain = middlewares.map(middleware => middleware({
  getState: store.getState, // 因为闭包 执行中间件的时候store不会更新
  dispatch: action => dispatch(action)
}));
// 调用原生的dispatch重写dispatch逻辑 只有最后一个中间件会接受真实的dispatch方法
dispatch = compose(...chain)(store.dispatch);
return {
  ...store,
  dispatch
}


这里可以看到中间件的设计确实十分巧妙,利用柯里化的结构「可以方便的访问相同的store,还能够配合compose方法,积累参数达到延迟执行的效果」,关于compose方法的说明就不列出来了,主要是通过reduce方法从右到左整合中间件。

其实Redux源码的内容还有其他值得细细品味的地方,以后有机会再写吧,接下来我们看一个新鲜的面孔。

下一代React状态管理器

自从React16以来,大家都纷纷用起了Hooks,毫无疑问,Hooks的出现在一定程度上解决了组件间功能复用的问题,这种逻辑的封装和复用确实很香,但还存在某些问题,比如说数据的共享。hox,被称为下一代React状态管理器,就是为了解决这类问题。

进入项目的Github,这里的Features很有意思:「只有一个API;使用 custom Hooks 来定义 model;完美拥抱TypeScript;支持多数据源」。不得不说这个one API很吸引人,我们先看看官方示例:

// 定义Model
import { createModel } from 'hox';
/* 任意一个 custom Hook */
function useCounter() {
  const [count, setCount] = useState(0);
  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  return {
    count,
    decrement,
    increment
  };
}
export default createModel(useCounter)
--------------------------------------------------------------------------------------------
// 使用Model
import { useCounterModel } from "../models/useCounterModel";


function App(props) {
  const counter = useCounterModel();
  return (
    <div>
      <p>{counter.count}</p>
      <button onClick={counter.increment}>Increment</button>
    </div>
  );
}
// 这个时候useCounterModel 是一个真正的 Hook,会订阅数据的更新。也就是说,当点击 “Increment” 按钮时,会触发 counter model 的更新,并且最终通知所有使用 useCounterModel 的组件或 Hook。

这里的createModel类似HOC的使用,其作用就是实现数据的共享,原理大概就是其内部实现了个发布订阅器,每当Model进行重渲染时,会通知其订阅者重新进行渲染。有兴趣的童鞋们不妨直接去看源码,其核心内容并不多。

(其实看到hox的时候发现和我之前写的一个小程序版的状态管理插件有点像,都是一个API实现全局数据共享,这里厚脸皮的贴上Github地址 createStore。)

总结

从前端状态管理的起源,到React的实现历程分析,简单的分享了一些自己的看法,文中有不够准确的地方欢迎交流指正。

最后,520快乐,感谢阅读!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章