Most of our internal products have made extensive use of DOB to manage front-end data flow, which will be introduced here.

Dob is a data dependency tracking tool implemented by proxy, combining doB-React with React.

Dob borrows a lot from MOBx at its core, but has been greatly improved in terms of implementation principles, ease of use, and debugging tools.

Characteristics of the

  • βœ… support
  • ❌ does not support
  • πŸ“¦ Ecological Support
  • 🀷 not fully supported
function redux mobx dob
asynchronous πŸ“¦redux-thunk η­‰ βœ… βœ…
Dating back βœ… πŸ“¦ mst βœ…
The fractal 🀷 replaceReducer βœ… βœ…
Code to streamline πŸ“¦ dva η­‰ βœ… βœ…
functional βœ… 🀷 🀷
object-oriented 🀷 βœ… βœ…
Typescript support 🀷 βœ… βœ…
A debugging tool βœ… βœ… βœ…
The debugger action is bidirectionally bound to the UI ❌ 🀷 βœ…
Strict mode βœ… βœ…
Supports types such as native Map ❌ βœ…
Observable syntax naturalness ❌ βœ…
Store standardization βœ… 🀷 βœ…

Start with dependency tracing

The DOB itself only implements dependency tracing, which is very simple, as shown in the following diagram + code:

img

import { observable, observe } from "dob"

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

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

Observable an object generated by observable, used in the Observe callback, that is reexecuted when the object is modified.

React works beautifully with React

Use this feature to replace Observe with the Render function of the React framework.

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)Copy the code

That’s exactly what Dob-React does.

The above combination is too arbitrary, which is not conducive to project maintenance. Real DOB-React limits the use of DOB.

Global data flow

To better manage the global data flow, we introduce the concept of action, store, component can only trigger action, only within the action can modify store:

img

Since aggregating stores into React is very easy, just provider@connect is all you need to do, so organize the relationship between store and action to organize the entire application structure.

How do you organize actions, stores, and React? Dob provides a mature pattern for global data flow: dependency injection. The following are good maintainability patterns:

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)Copy the code

The React component automatically injects the aggregate store into the action via @Connect.

Local data flow

Data that is not sensitive to global state can be treated as a local data stream.

The @Connect decorator injects all of the Provider’s parameters into the component if it takes no parameters. If the parameter is an object, it injects the object into the current component in addition to the global data flow, thus implementing the local data flow.

React #Connect

The structure is shown in the figure below:

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>)}}Copy the code

PS: Local data stream can replace setState to manage the state of the component itself, creating a local data stream bound to it every time the component is instantiated. If you don’t want to use the setState provided by react, you can use local data streams instead.

Asynchrony & Side effects

In REdux, the side effect code needs to be removed from reducer, but not from DOB. We can write action as follows:

@Action async getUserInfo() {
    this.UserStore.loading = true
    this.UserStore.currentUser = await fetchUser()
    this.UserStore.loading = false

    try {
        this.UserStore.articles = await fetchArticle()
    } catch(error) {
        // Silence failed}}Copy the code

Devtools

Using dob-react-devtools to enable the debug mode, you can achieve the effect similar to redux-DevTools, but the debugging tool has the function of action and UI visual binding, etc.

  • Ui-action binding: The UI element, when rerender is triggered, is itself highlighted and displays the number of renders in the upper left corner, along with the action that caused it to render.
  • Action bound to UI: After expanding the action list on the right, rerender UI elements are displayed via hover, highlighted.
  • Manage actions by searching, clearing, etc.
  • Tap the bulb to turn debug on/off.

Suppose we have an article list requirement, and we create ArticleStore and ArticleAction, which provide basic methods like addArticle, removeArticle, and changeArticleTitle.

Now we have debugging enabled and get the following GIF:

dob-react-devtools

Dob-react-devtools mainly provides a visual interface to display the trigger list of each Action. Moving the mouse over each Action will highlight the UI element corresponding to rerender. The upper-left sidebar also lists the actions associated with this UI element.