我们大部分对内产品,都广泛使用了 管理前端数据流,下面隆重介绍一下。
dob 是利用 proxy 实现的数据依赖追踪工具,利用 与 react 结合。
dob 的核心思想大量借鉴了 ,但是从实现原理、使用便捷性,以及调试工具都做了大量优化。
特征
- ✅ 支持
- ❌ 不支持
- ? 生态支持
- ? 不完全支持
功能 | redux | mobx | dob |
---|---|---|---|
异步 | ? 等 | ✅ | ✅ |
可回溯 | ✅ | ? | ✅ |
分形 | ? | ✅ | ✅ |
代码精简 | ? 等 | ✅ | ✅ |
函数式 | ✅ | ? | ? |
面向对象 | ? | ✅ | ✅ |
Typescript 支持 | ? | ✅ | ✅ |
调试工具 | ✅ | ✅ | ✅ |
调试工具 action 与 UI 双向绑定 | ❌ | ? | ✅ |
严格模式 | ✅ | ✅ | |
支持原生 Map 等类型 | ❌ | ✅ | |
observable 语法自然度 | ❌ | ✅ | |
store 规范化 | ✅ | ? | ✅ |
从依赖追踪开始
dob 自己只实现了依赖追踪功能,其特性非常简单,如下示意图+代码所示:
import { observable, observe } from "dob"const obj = observable({ a: 1, b: 1 })observe(() => { console.log(obj.a)})复制代码
一句话描述就是:由
observable
产生的对象,在observe
回调函数中使用,当这个对象被修改时,会重新执行这个回调函数。
与 react 优雅结合
那么利用这个特性,将 observe 换成 react 框架的 render 函数,就变成了下图:
import { observable, observe } from "dob"import { Provider, Connect } from 'dob-react'const obj = observable({ a: 1 })@Connectclass App extends React.Component { render() { return ( { this.props.store.a = 2 }}> {this.props.store.a} ) }}ReactDOM.render(, dom)复制代码
这正是 做的工作。
上面这种结合随意性太强,不利于项目维护,真正的 dob-react 对 dob 的使用方式做了限制。
全局数据流
为了更好管理全局数据流,我们引入 action、store 的概念,组件只能触发 action,只有 action 内部才能修改 store:
由于聚合 store 注入到 react 非常简单,只需要 Provider
@Connect
即可,所以组织好 store 与 action 的关系,也就组织好了整个应用结构。
那么如何组织 action、store、react 之间的关系呢?对全局数据流,dob 提供了一种成熟的模式:依赖注入。以下是可维护性良好模式:
import { Action, observable, combineStores, inject } from 'dob'import { Provider, Connect } from 'dob-react'@observableexport class UserStore { name = 'bob'}export class UserAction { @inject(UserStore) private UserStore: UserStore; @Action setName () { this.store.name = 'lucy' }}@Connectclass App extends React.Component { render() { return ( {this.props.UserStore.name} ) }}ReactDOM.render(, dom)复制代码
一句话描述就是:通过
combineStores
聚合 store 与 action,store 通过inject
注入到 action 中被修改,react 组件通过@Connect
自动注入聚合 store。
局部数据流
对于对全局状态不敏感的数据,可以作为局部数据流处理。
@Connect
装饰器如果不带参数,会给组件注入 Provider
所有参数,如果参数是一个对象,除了注入全局数据流,还会把这个对象注入到当前组件,由此实现了局部数据流。
PS: Connect 函数更多用法可以参考文档:
结构如下图所示:
import { Action, observable, combineStores, inject } from 'dob'import { Provider, Connect } from 'dob-react'@observableexport class UserStore { name = 'bob'}export class UserAction { @inject(UserStore) private UserStore: UserStore; @Action setName () { this.store.name = 'lucy' }}@Connect(combineStores(UserStore, UserAction))class App extends React.Component { render() { return ( {this.props.UserStore.name} ) }}复制代码
PS: 局部数据流可以替代 setState
管理组件自身状态,每当组件被实例化一次,就会创建一个与之绑定的局部数据流。如果不想使用 react 提供的 setState,可以使用局部数据流替代。
异步 & 副作用
redux 中需要将副作用代码从 reducer 抽离,而 dob 不需要,我们可以如下书写 action:
@Action async getUserInfo() { this.UserStore.loading = true this.UserStore.currentUser = await fetchUser() this.UserStore.loading = false try { this.UserStore.articles = await fetchArticle() } catch(error) { // 静默失败 }}复制代码
Devtools
借助 开启调试模式,可以实现类似 的效果,但,该调试工具具备 action 与 UI 双向可视化绑定 的功能等:
- UI 与 action 绑定:ui 元素触发 rerender 时,自身会高亮,并在左上角显示渲染次数,以及导致其 render 的 action。
- action 与 UI 绑定:展开右侧 action 列表后,通过 hover 可展示因此 action 触发而 rerender 的 UI 元素,高亮出来。
- 搜索、清空等方式管理 action。
- 点击灯泡 开启/关闭 debug 模式。
假设现在有一个文章列表需求,我们创建了 ArticleStore
与 ArticleAction
,ArticleAction
提供了 addArticle, removeArticle, changeArticleTitle 等基础方法。
现在我们开启了调试功能,获得如下 gif 图的效果:
dob-react-devtools 主要提供了可视化界面展示每个 Action 触发列表,鼠标移动到每个 Action 会高亮对应 rerender 的 UI 元素,UI 元素 render 的时候,左上角工具条也列出了与这个 UI 元素相关的 Action 列表。