阅读 92

Mobx源码解析-observable

前言

最近一直在用Mobx开发中小型项目,开发起来真的,真的很爽,响应式更新,性能快,样板代码减少(相对Redux)。所以,想趁2019年结束前把Mobx源码研究一遍。

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最基础的使用方式 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>
        );
    }
}
复制代码

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;
复制代码

界面如下

image
功能非常简单,实现也非常简单。通过observablecount进行了监听,只要count产生了数据变化,就会自动刷新界面。那么,Mobx是如何做到的呢?让我们一步步来分析。

observable

首先,看入口文件,mobx-source -> mobx.js,发现observable,action,runInAction等其他方法都是从internal引入的。

export { observable, action, runInAction } from "./internal";
复制代码

打开internal.js

export * from "./api/action";
export * from "./api/autorun";
export * from "./api/observable";
复制代码

然后看api/observable这个文件,发现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;
}
复制代码

createObservable主要做了以下几件事:

1、如果被观察的对象是stringsymbol,那么执行deepDecorator.apply(null, arguments); export const deepDecorator = createDecoratorForEnhancer(deepEnhancer);deepEnhancer方法内部会判断当前修改的值类型,来走不同的工厂方法。

2、如果第一个参数已经是一个可被观察的对象,那么返回这个对象。

3、对第一个参数进行类型(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

关注下面的标签,发现更多相似文章
评论