preface

I’ve been working on small to medium sized projects with MobX lately, and it’s really, really fun to develop with responsive updates, fast performance, and less boilerplate code (versus Redux). So, I want to take a look at the MobX source code before 2019 is over.

Tips

  • Due to theMobXThe source code is very large, so will only put the personal view of the more important part of the interception
  • readingMobXSource version @5.15.0
  • Because of my interest inTypeScriptI’m not very experienced, so I’ll compile it intoJavaScriptreading
  • The following will usemobx-sourceReferred to as “instead ofMobx

How to debug source code

  • $ git clone https://github.com/mobxjs/mobx.git
  • $ cd mobx
  • $ cnpm i
  • To viewpackage.json, the execution script is found to havequick-buildsmall-buildAnd I chose yessmall-build.cnpm run small-buildIt is then generated in the root directory.build.es5.build.es6
"scripts": {
    "quick-build": "tsc --pretty",
    "small-build": "node scripts/build.js"
},
Copy the code
  • Rename.build.es6 to Mobx-Source and put it in the scaffolding I wrote

  • Introducing absolute paths

import { observable, action } from '.. /.. /mobx-source/mobx';Copy the code
  • Then you can have fun debugging the source code
function createObservable(v, arg2, arg3) { debugger; . }Copy the code

Demo

Let’s start with counters and look at the most basic MobX use of React

@inject('counterStore') @observer class Index extends Component { constructor(props) { super(props); } render() { const { counterStore } = this.props; return ( <section> <button onClick={() => counterStore.add()}>+</button> <span>count is: {counterStore.obj.count}</span> <button onClick={() => counterStore.reduce()}>-</button> </section> ); }}Copy the code

MobX

import { observable, action } from '.. /.. /mobx-source/mobx'; class CounterStore { @observable obj = { count: 0 }; @action add() { this.obj.count++; } @action reduce() { this.obj.count--; } } export default CounterStore;Copy the code

The interface is as follows

The functionality is very simple and the implementation is very simple. Observable monitors count and automatically refreshes the interface whenever it changes data. So how does MobX do it? Let’s take it step by step.

observable

First, look at the entry file, mobx-source -> mobx.js, and see that observable, Action, runInAction, and other methods are imported from internal.

export { observable, action, runInAction } from "./internal";
Copy the code

Open the internal. Js

export * from "./api/action";
export * from "./api/autorun";
export * from "./api/observable";
Copy the code

Export const Observable = createObservable;

function createObservable(v, arg2, arg3) { // @observable someProp; if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") { return deepDecorator.apply(null, arguments);  } // it is an observable already, done if (isObservable(v)) return v; // something that can be converted and mutated? const res = isPlainObject(v) ? observable.object(v, arg2, arg3) : Array.isArray(v) ? observable.array(v, arg2) : isES6Map(v) ? observable.map(v, arg2) : isES6Set(v) ? observable.set(v, arg2) : v; }Copy the code

CreateObservable does a few things:

Deepdecorator.apply (null, arguments) if the object being observed is a string or symbol; export const deepDecorator = createDecoratorForEnhancer(deepEnhancer); The deepEnhancer method internally determines the type of value being modified to go through the different factory methods.

2. If the first argument is already an observable object, return that object.

3. Determine the type (Object, array, Map, set) of the first parameter and call the different factory methods.

const observableFactories = {
    box(value, options) {
        ...
    },
    array(initialValues, options) {
        ...
    },
    map(initialValues, options) {
        ...
    },
    set(initialValues, options) {
        ...
    },
    object(props, decorators, options) {
        ...
    },
    ref: refDecorator,
    shallow: shallowDecorator,
    deep: deepDecorator,
    struct: refStructDecorator
};
Copy the code

Next, let’s analyze createDecoratorForEnhancer method, there are two main parameters, first the default is true, the second is a function. res.enhancer = enhancer; , will mount the deepEnhancer from above. Different observable parameters, such as Object and array, are called for hijacking based on variable types.

export function createDecoratorForEnhancer(enhancer) { invariant(enhancer); const decorator = createPropDecorator(true, (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) => { if (process.env.NODE_ENV ! == "production") { invariant(! descriptor || ! descriptor.get, `@observable cannot be used on getter (property "${stringifyKey(propertyName)}"), use @computed instead.`); } const initialValue = descriptor ? descriptor.initializer ? descriptor.initializer.call(target) : descriptor.value : undefined; asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer); }); const res = // Extra process checks, as this happens during module initialization typeof process ! == "undefined" && process.env && process.env.NODE_ENV ! == "production" ? function observableDecorator() { // This wrapper function is just to detect illegal decorator invocations, deprecate in a next version // and simply return the created prop decorator if (arguments.length < 2) return fail("Incorrect decorator invocation. @observable decorator doesn't expect any arguments"); return decorator.apply(null, arguments); } : decorator; res.enhancer = enhancer; return res; }Copy the code

The createPropDecorator method creates the property interceptor, and the addHiddenProp method adds the Symbol(Mobx Pending Decorators) property to the target object.

export function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) { return function decoratorFactory() { let decoratorArguments; const decorator = function decorate(target, prop, descriptor, applyImmediately // This is a special parameter to signal the direct application of a decorator, allow extendObservable to skip the entire type decoration part, // as the instance to apply the decorator to equals the target ) { ... if (! Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) { const inheritedDecorators = target[mobxPendingDecorators]; addHiddenProp(target, mobxPendingDecorators, Object.assign({}, inheritedDecorators)); } target[mobxPendingDecorators][prop] = { prop, propertyCreator, descriptor, decoratorTarget: target, decoratorArguments }; return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable); }; }; }Copy the code

Due to the above I define variable is an object, so Mobx take this object to intercept, execute observableFactories. Object

object(props, decorators, options) { if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("object"); const o = asCreateObservableOptions(options); if (o.proxy === false) { return extendObservable({}, props, decorators, o); } else { const defaultDecorator = getDefaultDecoratorFromObjectOptions(o); const base = extendObservable({}, undefined, undefined, o); const proxy = createDynamicObservableObject(base); extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator); return proxy; }},Copy the code

AsCreateObservableOptions create an observable object, as is the object, so the proxy is undefined, into the else, const base = extendObservable({}, undefined, undefined, o); O under processing object, into a Symbol data types, and then see createDynamicObservableObject, essential method, the function inside the Proxy is used to create interceptors, has the property of the object, get, set, The deleteProperty, ownKeys, preventExtensions methods do the proxy blocking.

export function createDynamicObservableObject(base) { const proxy = new Proxy(base, objectProxyTraps); base[$mobx].proxy = proxy; return proxy; } const objectProxyTraps = { has(target, name) { ... }, get(target, name) { ... }, set(target, name, value) { ... }, deleteProperty(target, name) { ... }, ownKeys(target) { ... }, preventExtensions(target) { ... }};Copy the code

extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator); Object properties are iterated over to create interceptors, and there’s a concept of transactions involved, which we’ll look at later.

blog

Welcome to my blog