Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面向未来的前端数据流框架 - dob #22

Open
ascoders opened this issue Oct 23, 2017 · 0 comments
Open

面向未来的前端数据流框架 - dob #22

ascoders opened this issue Oct 23, 2017 · 0 comments

Comments

@ascoders
Copy link
Owner

ascoders commented Oct 23, 2017

我们数据技术产品部有一部分只需要兼容最新版 chrome 对外产品,以及大部分对内产品,都广泛使用了 dob 管理前端数据流,下面隆重介绍一下。

dob 是利用 proxy 实现的数据依赖追踪工具,利用 dob-react 与 react 结合。

dob 的核心思想大量借鉴了 mobx,但是从实现原理、使用便捷性,以及调试工具都做了大量优化。

特征

  • ✅ 支持
  • ❌ 不支持
  • 📦 生态支持
  • 🤷 不完全支持
功能 redux mobx dob
异步 📦redux-thunk
可回溯 📦 mst
分形 🤷 replaceReducer
代码精简 📦 dva
函数式 🤷 🤷
面向对象 🤷
Typescript 支持 🤷
调试工具
调试工具 action 与 UI 双向绑定 🤷
严格模式
支持原生 Map 等类型
observable 语法自然度
store 规范化 🤷

从依赖追踪开始

dob 自己只实现了依赖追踪功能,其特性非常简单,如下示意图+代码所示:

img

import { observable, observe } from "dob"

const obj = observable({ a: 1, b: 1 })

observe(() => {
    console.log(obj.a)
})

一句话描述就是:由 observable 产生的对象,在 observe 回调函数中使用,当这个对象被修改时,会重新执行这个回调函数。

与 react 优雅结合

那么利用这个特性,将 observe 换成 react 框架的 render 函数,就变成了下图:

img

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:

img

由于聚合 store 注入到 react 非常简单,只需要 Provider @Connect 即可,所以组织好 store 与 action 的关系,也就组织好了整个应用结构。

那么如何组织 action、store、react 之间的关系呢?对全局数据流,dob 提供了一种成熟的模式:依赖注入。以下是可维护性良好模式

img

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

结构如下图所示:

img

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) {
		// 静默失败
	}
}

Devtools

借助 dob-react-devtools 开启调试模式,可以实现类似 redux-devtools 的效果,但,该调试工具具备 action 与 UI 双向可视化绑定 的功能等:

  • UI 与 action 绑定:ui 元素触发 rerender 时,自身会高亮,并在左上角显示渲染次数,以及导致其 render 的 action。
  • action 与 UI 绑定:展开右侧 action 列表后,通过 hover 可展示因此 action 触发而 rerender 的 UI 元素,高亮出来。
  • 搜索、清空等方式管理 action。
  • 点击灯泡 开启/关闭 debug 模式。

假设现在有一个文章列表需求,我们创建了 ArticleStoreArticleActionArticleAction 提供了 addArticle, removeArticle, changeArticleTitle 等基础方法。

现在我们开启了调试功能,获得如下 gif 图的效果:

dob-react-devtools

dob-react-devtools 主要提供了可视化界面展示每个 Action 触发列表,鼠标移动到每个 Action 会高亮对应 rerender 的 UI 元素,UI 元素 render 的时候,左上角工具条也列出了与这个 UI 元素相关的 Action 列表。

开启调试模式的方法:

import "dob-react-devtools"
import { startDebug } from "dob-react"

startDebug()

调试 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

git clone https://github.com/dobjs/dob-example.git

通过 npm i; npm start 快速运行起来,本项目包含了 dob 推荐的目录结构与 store 组织方式。本 demo 使用了 typescript、react@16 与 react-router@4。

生态

  • dob-react 让您在 react 中使用 dob!
  • dob-react-devtools - dob-react 的调试工具,具有 UI 元素与 Action 双向绑定特性。
  • dob-redux - 可以在 dob 中使用 redux 与 react-redux,同时具有 mutable 与 immutable 的优点!
  • dob-refect - 自动发请求,摆脱 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 之间的选择,凭团队或个人爱好或许会更好抉择。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant