Open ascoders opened 6 years ago
我们数据技术产品部有一部分只需要兼容最新版 chrome 对外产品,以及大部分对内产品,都广泛使用了 dob 管理前端数据流,下面隆重介绍一下。
dob 是利用 proxy 实现的数据依赖追踪工具,利用 dob-react 与 react 结合。
dob 的核心思想大量借鉴了 mobx,但是从实现原理、使用便捷性,以及调试工具都做了大量优化。
dob 自己只实现了依赖追踪功能,其特性非常简单,如下示意图+代码所示:
import { observable, observe } from "dob" const obj = observable({ a: 1, b: 1 }) observe(() => { console.log(obj.a) })
一句话描述就是:由 observable 产生的对象,在 observe 回调函数中使用,当这个对象被修改时,会重新执行这个回调函数。
observable
observe
那么利用这个特性,将 observe 换成 react 框架的 render 函数,就变成了下图:
import { observable, observe } from "dob" import { Provider, Connect } from 'dob-react' const obj = observable({ a: 1 }) @Connect class App extends React.Component { render() { return ( <span onClick={() => { this.props.store.a = 2 }}> {this.props.store.a} </span> ) } } ReactDOM.render( <Provider store={obj}> <App/> </Provider> , dom)
这正是 dob-react 做的工作。
上面这种结合随意性太强,不利于项目维护,真正的 dob-react 对 dob 的使用方式做了限制。
为了更好管理全局数据流,我们引入 action、store 的概念,组件只能触发 action,只有 action 内部才能修改 store:
由于聚合 store 注入到 react 非常简单,只需要 Provider @Connect 即可,所以组织好 store 与 action 的关系,也就组织好了整个应用结构。
Provider
@Connect
那么如何组织 action、store、react 之间的关系呢?对全局数据流,dob 提供了一种成熟的模式:依赖注入。以下是可维护性良好模式:
import { Action, observable, combineStores, inject } from 'dob' import { Provider, Connect } from 'dob-react' @observable export class UserStore { name = 'bob' } export class UserAction { @inject(UserStore) private UserStore: UserStore; @Action setName () { this.store.name = 'lucy' } } @Connect class App extends React.Component { render() { return ( <span onClick={this.props.UserAction.setName}> {this.props.UserStore.name} </span> ) } } ReactDOM.render( <Provider { ...combineStores({ UserStore, UserAction }) }> <App /> </Provider> , dom)
一句话描述就是:通过 combineStores 聚合 store 与 action,store 通过 inject 注入到 action 中被修改,react 组件通过 @Connect 自动注入聚合 store。
combineStores
inject
对于对全局状态不敏感的数据,可以作为局部数据流处理。
@Connect 装饰器如果不带参数,会给组件注入 Provider 所有参数,如果参数是一个对象,除了注入全局数据流,还会把这个对象注入到当前组件,由此实现了局部数据流。
PS: Connect 函数更多用法可以参考文档: dob-react #Connect
结构如下图所示:
import { Action, observable, combineStores, inject } from 'dob' import { Provider, Connect } from 'dob-react' @observable export 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 ( <span onClick={this.props.UserAction.setName}> {this.props.UserStore.name} </span> ) } }
PS: 局部数据流可以替代 setState 管理组件自身状态,每当组件被实例化一次,就会创建一个与之绑定的局部数据流。如果不想使用 react 提供的 setState,可以使用局部数据流替代。
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) { // 静默失败 } }
借助 dob-react-devtools 开启调试模式,可以实现类似 redux-devtools 的效果,但,该调试工具具备 action 与 UI 双向可视化绑定 的功能等:
假设现在有一个文章列表需求,我们创建了 ArticleStore 与 ArticleAction,ArticleAction 提供了 addArticle, removeArticle, changeArticleTitle 等基础方法。
ArticleStore
ArticleAction
现在我们开启了调试功能,获得如下 gif 图的效果:
dob-react-devtools 主要提供了可视化界面展示每个 Action 触发列表,鼠标移动到每个 Action 会高亮对应 rerender 的 UI 元素,UI 元素 render 的时候,左上角工具条也列出了与这个 UI 元素相关的 Action 列表。
开启调试模式的方法:
import "dob-react-devtools" import { startDebug } from "dob-react" startDebug()
调试 UI 元素将自动附着在 Provider 元素上。
一旦开启调试模式,在 dob 依赖追踪的 getter setter 处就会增加调用信息的存储(因此开启调试模式时性能会一定程度下降,且更加吃内存)。
getter
setter
由于 react render 函数是同步的(16 支持的异步渲染模式,在执行到 render 时也是同步的),只要包裹在 Action 中的变量存取,都可以在结束时打上唯一 id,当 react render 执行时,将当前 id 推送给调试工具,即可将 UI 与 Action 一一绑定。
action 与 debug 调用顺序:startBatch -> debugInAction -> ...multiple nested startBatch and endBatch -> debugOutAction -> reaction -> observe。
git clone https://github.com/dobjs/dob-example.git
通过 npm i; npm start 快速运行起来,本项目包含了 dob 推荐的目录结构与 store 组织方式。本 demo 使用了 typescript、react@16 与 react-router@4。
npm i; npm start
componentDidUpdate
mobx 即将到来的 4.0 版本也要支持 proxy 了:Road to 4.0 · Issue #1076 · mobxjs/mobx
届时其性能与实用度将与 dob 越来越接近,但现在 dob 已凭借大量使用经验进行优化,对 devtools 的支持也更为抢眼,UI 与 Action 双向绑定 debug 模式应该是首创。
所以至于选择 mobx 还是 dob,全凭个人喜好,也许等到 4.0,mobx 会变得和 dob 一样好用,现在,你可以通过 dob 预先尝试抛弃 IE 的爽快开发体验,以及独具特色的 devTools。
至于 redux/rxjs 与 mobx/dob 之间的选择,凭团队或个人爱好或许会更好抉择。
我们数据技术产品部有一部分只需要兼容最新版 chrome 对外产品,以及大部分对内产品,都广泛使用了 dob 管理前端数据流,下面隆重介绍一下。
dob 是利用 proxy 实现的数据依赖追踪工具,利用 dob-react 与 react 结合。
dob 的核心思想大量借鉴了 mobx,但是从实现原理、使用便捷性,以及调试工具都做了大量优化。
特征
从依赖追踪开始
dob 自己只实现了依赖追踪功能,其特性非常简单,如下示意图+代码所示:
与 react 优雅结合
那么利用这个特性,将 observe 换成 react 框架的 render 函数,就变成了下图:
这正是 dob-react 做的工作。
上面这种结合随意性太强,不利于项目维护,真正的 dob-react 对 dob 的使用方式做了限制。
全局数据流
为了更好管理全局数据流,我们引入 action、store 的概念,组件只能触发 action,只有 action 内部才能修改 store:
由于聚合 store 注入到 react 非常简单,只需要
Provider
@Connect
即可,所以组织好 store 与 action 的关系,也就组织好了整个应用结构。那么如何组织 action、store、react 之间的关系呢?对全局数据流,dob 提供了一种成熟的模式:依赖注入。以下是可维护性良好模式:
局部数据流
对于对全局状态不敏感的数据,可以作为局部数据流处理。
@Connect
装饰器如果不带参数,会给组件注入Provider
所有参数,如果参数是一个对象,除了注入全局数据流,还会把这个对象注入到当前组件,由此实现了局部数据流。结构如下图所示:
PS: 局部数据流可以替代
setState
管理组件自身状态,每当组件被实例化一次,就会创建一个与之绑定的局部数据流。如果不想使用 react 提供的 setState,可以使用局部数据流替代。异步 & 副作用
redux 中需要将副作用代码从 reducer 抽离,而 dob 不需要,我们可以如下书写 action:
Devtools
借助 dob-react-devtools 开启调试模式,可以实现类似 redux-devtools 的效果,但,该调试工具具备 action 与 UI 双向可视化绑定 的功能等:
假设现在有一个文章列表需求,我们创建了
ArticleStore
与ArticleAction
,ArticleAction
提供了 addArticle, removeArticle, changeArticleTitle 等基础方法。现在我们开启了调试功能,获得如下 gif 图的效果:
开启调试模式的方法:
调试 UI 元素将自动附着在
Provider
元素上。Devtools 双向绑定原理解读
一旦开启调试模式,在 dob 依赖追踪的
getter
setter
处就会增加调用信息的存储(因此开启调试模式时性能会一定程度下降,且更加吃内存)。由于 react render 函数是同步的(16 支持的异步渲染模式,在执行到 render 时也是同步的),只要包裹在 Action 中的变量存取,都可以在结束时打上唯一 id,当 react render 执行时,将当前 id 推送给调试工具,即可将 UI 与 Action 一一绑定。
action 与 debug 调用顺序:startBatch -> debugInAction -> ...multiple nested startBatch and endBatch -> debugOutAction -> reaction -> observe。
快速上手 Demo
通过
npm i; npm start
快速运行起来,本项目包含了 dob 推荐的目录结构与 store 组织方式。本 demo 使用了 typescript、react@16 与 react-router@4。生态
componentDidUpdate
的困扰。总结
mobx 即将到来的 4.0 版本也要支持 proxy 了:Road to 4.0 · Issue #1076 · mobxjs/mobx
届时其性能与实用度将与 dob 越来越接近,但现在 dob 已凭借大量使用经验进行优化,对 devtools 的支持也更为抢眼,UI 与 Action 双向绑定 debug 模式应该是首创。
所以至于选择 mobx 还是 dob,全凭个人喜好,也许等到 4.0,mobx 会变得和 dob 一样好用,现在,你可以通过 dob 预先尝试抛弃 IE 的爽快开发体验,以及独具特色的 devTools。
至于 redux/rxjs 与 mobx/dob 之间的选择,凭团队或个人爱好或许会更好抉择。