I used to use Redux a lot. I’ve always heard that Mobx makes you feel like writing Vue in React. Today I’m going to try to see if Mobx really feels like writing Vue in React.

digression

Before introducing the usage of MobX, let’s take a look at the Introduction of MobX in Chinese. On MobX’s Chinese website, it says:

MobX is a battle-tested library that makes state management simple and extensible through transparent functional responsive programming.

“War of War library” is very strange to look at, it is very difficult to read 😂, and many articles about MobX on the Internet are written in this way, on Github read its README, it says:

MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).

You can see what the author intended to say is that MobX has been tested a lot and is robust. Here’s the result via Google Translate, which also looks a little more accurate than the Chinese version.

Although my English level is also very poor, I will try to read official documents, so as to avoid some unnecessary misunderstandings.

How to use it?

Anyway, the latest version of MobX is 6.0, and the API is much simpler and more user-friendly than before. The previous version was a decorator-style syntax sugar, but decorators are not mature in the current ES specification, and the introduction of decorator syntax increases the size of packaged code. All things considered, MobX 6.0 removed the DECORator syntax API.

Responsive object

MobX constructs responsive objects using makeObservable methods. Incoming Object properties are proxy-like. Prior to Vue, the Object.defineProperty API was used.

import { configure, makeObservable, observable, action, computed } from 'mobx'

// With this configuration, you can degrade the Proxy to Object.defineProperty
configure({ useProxies: "never" });

// Construct the response object
const store = makeObservable(
  // The response object that needs the proxy
  {
    count: 0.get double() {
      return this.count * 2
    },
    increment() {
      this.count += 1
    },
    decrement() {
      this.count -= 1}},// Wrap each attribute to mark what the attribute does
  {
    count: observable, // Response attributes to trace
    double: computed,  // Calculate attributes
    increment: action, // After the action is called, the response object is modified
    decrement: action, // After the action is called, the response object is modified})Copy the code

Let’s take a look at the previous version of MobX and write it as a decorator:

class Store {
  @observable count = 0
  constructor() {
    makeObservable(this)
  }
  @action increment() {
    this.count++;
  }
  @action decrement() {
    this.count--;
  }
  @computed get double() {
    return this.count * 2}}const store = new Store()
Copy the code

It doesn’t look like it’s simplified, it looks like it’s more complicated than writing decorators. Let’s take a look at a more powerful API in version 6.0: makeAutoObservable.

MakeAutoObservable is a more powerful makeObservable that automatically wraps object functions around properties, lowering the cost of getting started.

import { makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.get double() {
    return this.count * 2
  },
  increment() {
    this.count += 1
  },
  decrement() {
    this.count -= 1}})Copy the code

Calculate attribute

A MobX property, like Vue’s computed, is a getter in makeAutoObservable, and when the value that the getter depends on changes, the return value of the getter itself changes.

import { makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.get double() {
    return this.count * 2}})Copy the code

When store.count is 1, a call to store.double returns 2.

Modify the behavior

When we need to modify the response property on the Store, we can do so by reassigning the value directly, but this will be warned by MobX ⚠️.

const store = makeAutoObservable({
  count: 0
});

document.getElementById("increment").onclick = function () {
  store.count += 1
}
Copy the code

MobX will prompt you to modify the properties of a reactive object through action. Although direct changes can also work, they make MobX state management confusing, and putting state changes in action allows MobX to make changes in its internal transaction process, so as not to get an intermediate property and end up with inaccurate calculation results.

All methods in the makeAutoObservable are processed as actions.

import { makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.get double() {
    return this.count * 2
  },
  increment() { // action
    this.count += 1
  },
  decrement() { // action
    this.count -= 1}})Copy the code

Different from Vuex, state changes are divided into mutation and Action. Synchronous changes are placed into mutation and asynchronous operations are placed into Action. In MobX, both synchronous and asynchronous actions can be placed in the Action, except that asynchronous actions, when modifying properties, require the assignment action to be placed in the runInAction.

import { runInAction, makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.async initCount() {
    // Get remote data
    const count = await new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(10)},500)})// After retrieving the data, place the assignment operation in the runInAction
    runInAction(() = > {
      this.count = count
    })
  }
})

store.initCount()
Copy the code

If you do not invoke the runInAction, you can directly invoke an action that already exists.

import { runInAction, makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.setCount(count) {
    this.count = count
  },
  async initCount() {
    // Get remote data
    const count = await new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(10)},500)})// After the data is retrieved, call the existing action
    this.setCount(count)
  }
})

store.initCount()
Copy the code

Listening object change

Either React or MobX in applets requires that when an object changes, the native setState/setData methods are called to synchronize the state to the view.

This capability is implemented through the Autorun method, which we can think of as useEffect in React Hooks. The effect passed to Autorun is called every time the store’s response property is modified.

import { autorun, makeAutoObservable } from 'mobx'

const store = makeAutoObservable({
  count: 0.setCount(count) {
    this.count = count
  },
  increment() {
    this.count++
  },
  decrement() {
    this.count--
  }
})

document.getElementById("increment").onclick = function () {
  store.count++
}

const $count = document.getElementById("count")
$count.innerText = `${store.count}`
autorun(() = > {
  $count.innerText = `${store.count}`
})
Copy the code

Whenever the button#increment button is clicked, the values in span#count are automatically synchronized. 👉 View the full code.

In addition to Autorun, MobX also offers more refined listening methods: Reaction and WHEN.

const store = makeAutoObservable({
  count: 0.setCount(count) {
    this.count = count
  },
  increment() {
    this.count++
  },
  decrement() {
    this.count--
  }
})

// Call effect immediately after store changes
autorun(() = > {
  $count.innerText = `${store.count}`
});

// Effect is called only after the return value of the first method is modified
reaction(
  // Store. Count will not be called until it is modified
  () = > store.count,
  // The first parameter is the current value, and the second parameter is the value before modification
  // Similar to watch in Vue
  (value, prevValue) = > {
    console.log('diff', value - prevValue)
  }
);

// If the first method returns true, effect is immediately called
when(() = > store.count > 10.() = > {
  console.log(store.count)
})
// The when method also returns a promise
(async function() {
  await when(() = > store.count > 10)
  console.log('store.count > 10')
})()
Copy the code

conclusion

This is the end of MobX’s introduction, this article is just a general list of MobX apis, I hope you learned. I plan to further study the implementation of MobX in the future, and then write an article to share it after I finish my research.