我們大部分對內產品,都廣泛使用了 dob 管理前端資料流,下面隆重介紹一下。
dob 是利用 proxy 實現的資料依賴追蹤工具,利用 dob-react 與 react 結合。
dob 的核心思想大量借鑑了 mobx,但是從實現原理、使用便捷性,以及除錯工具都做了大量優化。
特徵
- ✅ 支援
- ❌ 不支援
- ? 生態支援
- ? 不完全支援
功能 | redux | mobx | dob |
---|---|---|---|
非同步 | ?redux-thunk 等 | ✅ | ✅ |
可回溯 | ✅ | ? mst | ✅ |
分形 | ? replaceReducer | ✅ | ✅ |
程式碼精簡 | ? dva 等 | ✅ | ✅ |
函式式 | ✅ | ? | ? |
物件導向 | ? | ✅ | ✅ |
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 })
@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 的關係,也就組織好了整個應用結構。
那麼如何組織 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) {
// 靜默失敗
}
}複製程式碼
Devtools
藉助 dob-react-devtools 開啟除錯模式,可以實現類似 redux-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 列表。