Mobx源碼解析-observable

前言

最近一直在用Mobx開發中小型項目,開發起來真的,真的很爽,響應式更新,性能快,樣板代碼減小(相對Redux)。因此,想趁2019年結束前把Mobx源碼研究一遍。node

Tips

  • 因爲MobX的源碼很大,所以只會把我的認爲比較重要的部分截取說明
  • 閱讀的MobX源碼版本@5.15.0
  • 因爲本人對TypeScript經驗尚淺,因此我會將其編譯成JavaScript閱讀
  • 下面會用mobx-source簡稱代替Mobx

如何調試源碼

  • $ git clone https://github.com/mobxjs/mobx.git
  • $ cd mobx
  • $ cnpm i
  • 查看package.json,發現執行腳本有quick-buildsmall-build,我選擇的是small-buildcnpm run small-build 而後在根目錄下會生成.build.es5.build.es6
"scripts": {
    "quick-build": "tsc --pretty",
    "small-build": "node scripts/build.js"
},
  • .build.es6更名爲mobx-source放到我寫好的腳手架中

image

  • 引入絕對路徑
import { observable, action } from '../../mobx-source/mobx';
  • 而後就能夠愉快的調試源碼了
function createObservable(v, arg2, arg3) {
    debugger;
    ...
}

Demo

讓咱們從計數器開始,看看Mobx最基礎的使用方式
Reactreact

@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>
        );
    }
}

Mobxgit

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;

界面以下
image
功能很是簡單,實現也很是簡單。經過observablecount進行了監聽,只要count產生了數據變化,就會自動刷新界面。那麼,Mobx是如何作到的呢?讓咱們一步步來分析。es6

observable

首先,看入口文件,mobx-source -> mobx.js,發現observable,action,runInAction等其餘方法都是從internal引入的。github

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

打開internal.jsnpm

export * from "./api/action";
export * from "./api/autorun";
export * from "./api/observable";

而後看api/observable這個文件,發現export const observable = createObservable;json

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;
}

createObservable主要作了如下幾件事:api

一、若是被觀察的對象是stringsymbol,那麼執行deepDecorator.apply(null, arguments);
export const deepDecorator = createDecoratorForEnhancer(deepEnhancer); deepEnhancer方法內部會判斷當前修改的值類型,來走不一樣的工廠方法。app

二、若是第一個參數已是一個可被觀察的對象,那麼返回這個對象。函數

三、對第一個參數進行類型(object、array、map、set)判斷,而後調用不一樣的工廠方法。

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
};

接下來,咱們來分析createDecoratorForEnhancer方法,主要有兩個參數,第一個默認爲true,第二個是個函數。res.enhancer = enhancer;,會把上面傳的deepEnhancer,在此處進行掛載。根據變量不一樣類型,調用observable的不一樣參數,如 object, array 來進行劫持。

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;
}

createPropDecorator方法建立屬性攔截器,addHiddenProp方法爲目標對象添加Symbol(mobx pending decorators)屬性。

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);
        };
    };
}

因爲上面我定義的變量是對象,因此Mobx會把這個對象攔截,執行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;
    }
},

asCreateObservableOptions建立一個可觀察的對象,因爲已是object了,因此proxy爲undefined,則進else, const base = extendObservable({}, undefined, undefined, o);加工處理下o對象,轉成Symbol數據類型,而後看createDynamicObservableObject,很關鍵的方法,這個函數內部就是利用Proxy來建立攔截器,對這個對象的屬性has, get, set, deleteProperty, ownKeys,preventExtensions 方法進行了代理攔截。

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) {
        ...
    }
};

extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);,會對對象屬性遍歷,來建立攔截器,並且這裏面會牽扯到一個事務的概念,後面會分析事務。

博客

歡迎關注博客

最後

相關代碼已上傳至react-scaffolding-mobx

相關文章
相關標籤/搜索