Preface ✍ ️
Front-end scenarios are increasingly complex. At present, new projects use Vue, Angular, and React to manage the mapping relationship between data and view. Each of them has its own unique mechanism to manage component state and life cycle. State containers like Vuex, Ngrx, and Redux are used to manage important global states.
React is the main application in my work. In the project, I used Mobx as a supplement to React state management to speed up the efficiency of coding. This paper mainly records some usage of Mobx.
Introduce 📚
Mobx is a state management library, which has unique advantages in the description of state dependencies, just like writing formulas. It allows developers to more succinct declare the description of attribute state dependencies, and automatically complete the update of related dependencies, causing side effects.
Usage 🔧
Mobx is very flexible. You can directly apply observable features to an object, declare them in a class, or even write them directly into the properties of the React component class (no different from the class in Mobx’s perspective).
Directly decorate object
Objects wrapped directly by An Observable gain Mobx’s capabilities.
import * as mobx from "mobx";
// Declare an object to be observable
const myObj = mobx.observable({
a: 1,
b: 3.get c() {
return this.b * 2; }});// Register a side effect function
mobx.autorun((a)= > {
console.log("a", myObj.a);
});
mobx.autorun((a)= > {
console.log("c", myObj.c);
});
// Change the properties of this object
myObj.a = false;
myObj.a = "hello";
myObj.b = 4;
A 1 c 6 a false a hello c 8 */
Copy the code
You can already see some of Mobx’s features
- Make an object observable.
- The registered side effects function fires automatically when the object property changes.
- Side effects do not trigger when properties unrelated to side effects change, as in the previous example
myObj.b
, changes will not trigger only witha
About side effects. c
thisgetter
Attribute will beautorun
Side effects were recorded aboutb
Depend on, whenb
Changes occur, associationsc
Side effects can also be triggered.
Knowing the rules above, you can try it directly in your project.
Declare its attributes in a class asobservable
import { observable, computed, action, autorun, flow } from "mobx";
// Use the property decorator declaration
class SimpleStore {
@observable a = 1;
@observable c = 2;
@computed get b() {
return this.a * this.a + 1;
}
@action setA(a) {
this.a = a;
}
asyncUpdate = flow(function* () {
const next1 = yield new Promise(res= > setTimeout((a)= > res(3), 1000));
this.a = next1;
const next2 = yield new Promise(res= > setTimeout((a)= > res(4), 1000));
this.a = next2;
});
}
const store = new SimpleStore();
autorun((a)= > {
console.log(store.a);
});
store.setA(2);
store.asyncUpdate();
SetA (2) 3 // asyncUpdate() 1s 4 // asyncUpdate() 2s */
Copy the code
There are a few more things you can see from the observable declaration of the class:
- You need to use the decorator syntax for quick declarations
Mobx
Related functions require more input than working directly with objects, but can have more fine-grained control over their operation - The instantiated class object is also owned
observable
The ability to add to the field@observable
, the field will be recorded by the side effects. @action
It’s used to declare change@observable
Field method. If the following configuration is enabled, it is mandatory@action
Method to modify the property, otherwise an error is reported.
mobx.configure({
enforceActions: true
});
Copy the code
flow
isMobx
Provides a modifier for asynchronyaction
Methods. It’s really just aasync/await
methodsGenerator
Implementation, the best feature of this method is that it returns a Promise that can be cancelled.
inreact
Use in componentsMobx
Since the React component listens for observable changes, the render logic is a side effect. The correct way to use Autorun is to introduce the Mobx-React library. Import the Observer, a high-level component, to automatically register and destroy autorun.
Type of component
import * as React from "react"; import * as ReactDOM from "react-dom"; import { observable } from "mobx"; import { observer, Observer } from "mobx-react"; // React render extends class Counter extends React.Component {// It can be used as an Observable. Instead of react's own state, the update property is a bit more straightforward than setState. @observable unused = 0; handleInc = () => this.count++; handleDec = () => this.unused--; render() { console.log("render"); return ( <div> {this.count} <button onClick={this.handleInc}>+</button> <button onClick={this.handleDec}>-</button> </div> ); }}Copy the code
Unused does not result in rerendering because render only declares the use of count, and is wrapped with autorun for higher-order components. Autorun actually returns a value. Used to eliminate this side effect, but is automatically destroyed by the React unmount life cycle.
Function component
import * as React from "react"; import * as ReactDOM from "react-dom"; import { observable } from "mobx"; import { observer, Observer, useLocalStore } from "mobx-react"; Const Counter = Observer (() => {// Create a local Observable with useLocalStore const local = useLocalStore(() => ({ count: 0, unused: 0, handleInc() { this.count++; }, handleDec() { this.unused--; }})); console.log("render"); return ( <div> {local.count} <button onClick={() => local.handleInc()}>+</button> <button onClick={() => local.handleDec()}>-</button> </div> ); });Copy the code
Note: Use observer:
…
…
…
Outside of theobservable
object
You can create an Observable object or class directly outside and consume it with an Observer. Here’s how to use the global Store.
import * as React from "react"; import * as ReactDOM from "react-dom"; import { observable } from "mobx"; import { observer, Observer, useLocalStore } from "mobx-react"; const store1 = observable({ a: 1, b: "hello", incA() { this.a++; }, repeatB() { this.b += this.b; }, asyncIncA() { setTimeout(() => { this.a++; }, 1000); }}); //// {const stores = {store1}; type TStore = typeof stores; const storeCtx = React.createContext<TStore>(stores); const StoreProvider = ({ children }) => ( <storeCtx.Provider value={stores}>{children}</storeCtx.Provider> ); const useSore = () => React.useContext(storeCtx); ////} // Main code const UsingStore = observer(() => {const {store1} = useSore(); return ( <div> <div>a:{store1.a}</div> <div>b:{store1.b}</div> <button onClick={() => store1.incA()}>incA</button> <button onClick={() => store1.asyncIncA()}>asyncIncA</button> <button onClick={() => store1.repeatB()}>repeatB</button> </div> ); }); @observer class UsingStoreInClass extends React.Component { static contextType = storeCtx; render() { const { store1 } = this.context as TStore; return ( <div> <div>a:{store1.a}</div> <div>b:{store1.b}</div> <button onClick={() => store1.incA()}>incA</button> <button onClick={() => store1.repeatB()}>repeatB</button> </div> ); } } const App = () => { return ( <> <StoreProvider> <UsingStore /> <br /> <UsingStoreInClass /> <br /> </StoreProvider> < / a >); };Copy the code
The main piece of code is to create a store and put it into the Context, then class components and function components are decorated with an observer, take the global state out of the Context and use it, and when the global state is updated, the related components are notified and rerender.
Use the React lifecycle function to do this for you. The react lifecycle function does this for you. The react lifecycle function does this for you. In particular, the Hooks provided by React V16.8 are a good choice, and should only put critical global mutable state, such as user information, into the global Store.
Some skills
debugging
When an Observable wraps an object or property, it recursively converts it to an Observable, which is inconvenient to debug in console.log and full of proxies (if Mobx 5.x is used). You can use mobx.tojs to turn it into a normal object
To optimize the
In some cases, recursion to transform an attribute into an Observable is too fine-grained and unnecessary. In fact, it can reduce the Proxy overhead in this part by using observable.ref and Observable. shallow for the attribute. Or use option {deep:false} when creating an observable. Object, observable. Array, or Observable.
The chain reaction
In complex scenarios, computed attributes are often obtained asynchronously based on dependencies, so using computed is not appropriate. You can use multiple Observables and reaction to perform fetch logic.
import * as React from "react"; import * as ReactDOM from "react-dom"; import { observable, reaction, autorun } from "mobx"; import { observer, Observer, useLocalStore } from "mobx-react"; class ChainDemo { @observable a = 0; @observable b = 0; @observable c = 0; @observable d = 0; init = () => { const disposer = [ reaction( () => { const val = this.a; return new Promise<number>(res => setTimeout(() => res(val + 1), 100) ); }, async p => { this.b = await p; } ), reaction( () => { const val = this.b; return new Promise<number>(res => setTimeout(() => res(val + 1), 100) ); }, async p => { this.c = await p; } ), reaction( () => { const val = this.c; return new Promise<number>(res => setTimeout(() => res(val + 1), 100) ); }, async p => { this.d = await p; } ) ]; return () => disposer.forEach(d=>d()); }; } const chain = new ChainDemo(); chain.init(); autorun(() => { console.log(chain.a, chain.b, chain.c, chain.d); }); chain.a = 2; /** ** 0 0 0 0 * 2 0 0 0 * 2 3 0 0 * 2 3 4 0 * 2 3 4 5 * /Copy the code
In the above example, several delayed computed values are used, and the state is gradually updated according to the react chain we described. When the change is fast, more granular and controllable performance optimization can be done with flow and Debounce.
byreact
Department of Life Cycle Managementobservable
state
Continuing with the above code, a series of reactions returns to the Disposer of many disposers, so it is perfect to put this init directly to useEffect, taking advantage of the component’s life cycle to complete the initialization and destruction of the state.
const Comp = observer(() => {
const [state] = useState(() => new ChainDemo());
useEffect(state.init, [state]);
});
Copy the code
Give it a thumbs up if you think it’s good