咱们大部分对内产品,都普遍使用了 dob 管理前端数据流,下面隆重介绍一下。javascript
dob 是利用 proxy 实现的数据依赖追踪工具,利用 dob-react 与 react 结合。html
dob 的核心思想大量借鉴了 mobx,可是从实现原理、使用便捷性,以及调试工具都作了大量优化。前端
功能 | redux | mobx | dob |
---|---|---|---|
异步 | 📦redux-thunk 等 | ✅ | ✅ |
可回溯 | ✅ | 📦 mst | ✅ |
分形 | 🤷 replaceReducer | ✅ | ✅ |
代码精简 | 📦 dva 等 | ✅ | ✅ |
函数式 | ✅ | 🤷 | 🤷 |
面向对象 | 🤷 | ✅ | ✅ |
Typescript 支持 | 🤷 | ✅ | ✅ |
调试工具 | ✅ | ✅ | ✅ |
调试工具 action 与 UI 双向绑定 | ❌ | 🤷 | ✅ |
严格模式 | ✅ | ✅ | |
支持原生 Map 等类型 | ❌ | ✅ | |
observable 语法天然度 | ❌ | ✅ | |
store 规范化 | ✅ | 🤷 | ✅ |
dob 本身只实现了依赖追踪功能,其特性很是简单,以下示意图+代码所示:java
import { observable, observe } from "dob" const obj = observable({ a: 1, b: 1 }) observe(() => { console.log(obj.a) })
一句话描述就是:由
observable
产生的对象,在observe
回调函数中使用,当这个对象被修改时,会从新执行这个回调函数。react
那么利用这个特性,将 observe 换成 react 框架的 render 函数,就变成了下图:git
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 作的工做。github
上面这种结合随意性太强,不利于项目维护,真正的 dob-react 对 dob 的使用方式作了限制。redux
为了更好管理全局数据流,咱们引入 action、store 的概念,组件只能触发 action,只有 action 内部才能修改 store:api
因为聚合 store 注入到 react 很是简单,只须要 Provider
@Connect
便可,因此组织好 store 与 action 的关系,也就组织好了整个应用结构。框架
那么如何组织 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。
对于对全局状态不敏感的数据,能够做为局部数据流处理。
@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,可使用局部数据流替代。
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 等基础方法。
如今咱们开启了调试功能,得到以下 gif 图的效果:
dob-react-devtools 主要提供了可视化界面展现每一个 Action 触发列表,鼠标移动到每一个 Action 会高亮对应 rerender 的 UI 元素,UI 元素 render 的时候,左上角工具条也列出了与这个 UI 元素相关的 Action 列表。