Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MobX 源码解析-observable

MobX 源码解析-observable

原创
作者头像
发声的沉默者
修改于 2021-06-16 02:04:50
修改于 2021-06-16 02:04:50
7790
举报
文章被收录于专栏:江歌闲谈江歌闲谈

前言

最近一直在用 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
代码语言:txt
AI代码解释
复制
"scripts": {
    "quick-build": "tsc --pretty",
    "small-build": "node scripts/build.js"
},
  • .build.es6 改名为 mobx-source 放到我写好的脚手架中
代码语言:txt
AI代码解释
复制
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/501fc5efbdfc44769b2555bc25836f62~tplv-k3u1fbpfcp-zoom-1.image)
  • 引入绝对路径
代码语言:txt
AI代码解释
复制
import { observable, action } from '../../mobx-source/mobx';
  • 然后就可以愉快的调试源码了
代码语言:txt
AI代码解释
复制
function createObservable(v, arg2, arg3) {
    debugger;
    ...
}

Demo

让我们从计数器开始,看看 MobX 最基础的使用方式

React

代码语言:txt
AI代码解释
复制
@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

代码语言:txt
AI代码解释
复制
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
image

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

observable

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

代码语言:txt
AI代码解释
复制
export { observable, action, runInAction } from "./internal";

打开 internal.js

代码语言:txt
AI代码解释
复制
export * from "./api/action";
export * from "./api/autorun";
export * from "./api/observable";

然后看 api/observable 这个文件,发现 export const observable = createObservable;

代码语言:txt
AI代码解释
复制
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)判断,然后调用不同的工厂方法。

代码语言:txt
AI代码解释
复制
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 来进行劫持。

代码语言:txt
AI代码解释
复制
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) 属性。

代码语言:txt
AI代码解释
复制
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

代码语言:txt
AI代码解释
复制
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 了,所以 proxyundefined,则进 elseconst base = extendObservable({}, undefined, undefined, o); 加工处理下 o 对象,转成 Symbol 数据类型,然后看 createDynamicObservableObject,很关键的方法,这个函数内部就是利用 Proxy 来创建拦截器,对这个对象的属性 has, get, set, deleteProperty, ownKeys,preventExtensions 方法进行了代理拦截。

代码语言:txt
AI代码解释
复制
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);,会对对象属性遍历,来创建拦截器,而且这里面会牵扯到一个事务的概念,后面会分析事务。

博客

欢迎关注我的博客

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
各流派 React 状态管理对比和原理实现
在 React 诞生之初,Facebook 宣传这是一个用于前端开发的界面库。在大型应用中,如何处理好 React 组件通信和状态管理就显得非常重要。
尹光耀
2022/03/22
3.1K0
各流派 React 状态管理对比和原理实现
用故事解读 MobX源码(五) Observable
最高警长看完执行官(MobX)的自动部署方案,对 “观察员” 这个基层人员工作比较感兴趣,自执行官拿给他部署方案的时候,他就注意到所有上层人员的功能都是基于该底层人员高效的工作机制;
JSCON简时空
2020/04/01
8720
用故事解读 MobX源码(五) Observable
用故事解读 MobX源码(四) 装饰器 和 Enhancer
按照步骤,这篇文章应该写 观察值(Observable)的,不过在撰写的过程中发现,如果不先搞明白装饰器和 Enhancer(对这个单词陌生的,先不要着急,继续往下看) ,直接去解释观察值(Observable)会很费劲。因为在 MobX 中是使用装饰器设计模式实现观察值的,所以说要先掌握装饰器,才能进一步去理解观察值。
JSCON简时空
2020/03/31
9350
用故事解读 MobX源码(四) 装饰器 和 Enhancer
MobX
也就是说,只要知道哪些东西是状态相关的(源于应用状态),在状态发生变化时,就应该自动完成状态相关的所有事情,自动更新UI,自动缓存数据,自动通知server
ayqy贾杰
2019/06/12
1.2K0
MobX
实现简版 react 状态管理器 mobx
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
测不准
2022/08/07
1.5K0
实现简版 react 状态管理器 mobx
一文读懂 JS 装饰器,这是一个会打扮的装饰器
装饰器是最新的 ECMA 中的一个提案,是一种与类(class)相关的语法,用来注释或修改类和类方法。装饰器在 Python 和 Java 等语言中也被大量使用。装饰器是实现 AOP(面向切面)编程的一种重要方式。
用户1462769
2020/03/30
1.4K0
一文读懂 JS 装饰器,这是一个会打扮的装饰器
浅谈JS中的装饰器模式
装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写法与Java的注解(Annotation)非常相似,但是功能还是有很大区别。
IMWeb前端团队
2019/12/03
1.3K0
浅谈JS中的装饰器模式
TS 进阶 - 实际应用 03
装饰器的本质是一个函数,只不过它的入参时提前确定好的。TypeScript 中的装饰器目前只能在类及类成员上使用。
Cellinlab
2023/05/17
5070
一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础
作者:easonruan,腾讯 CSIG 前端开发工程师 1. 装饰器的样子 我们先来看看 Decorator 装饰器长什么样子,大家可能没在项目中用过 Decorator 装饰器,但多多少少会看过下面装饰器的写法: /* Nest.Js cats.controller.ts */ import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController {   @Get()  
腾讯技术工程官方号
2021/08/09
1.2K0
Javascript 装饰器极速指南
Decorators 是ES7中添加的JavaScript新特性。熟悉Typescript的同学应该更早的接触到这个特性,TypeScript早些时候已经支持Decorators的使用,而且提供了ES5的支持。本文会对Decorators做详细的讲解,相信你会体验到它给编程带来便利和优雅。 我在专职做前端开发之前, 是一名专业的.NET程序员,对.NET中的“特性”使用非常熟悉。在类、方法或者属性上写上一个中括号,中括号里面初始化一个特性,就会对类,方法或者属性的行为产生影响。这在AOP编程,以及ORM
用户1631416
2018/04/12
9530
Javascript 装饰器极速指南
全新 Javascript 装饰器实战上篇:用 MobX 的方式打开 Vue
去年三月份装饰器提案进入了 Stage 3 阶段,而今年三月份 Typescript 在 5.0 也正式支持了 。装饰器提案距离正式的语言标准,只差临门一脚。
_sx_
2023/10/20
6070
全新 Javascript 装饰器实战上篇:用 MobX 的方式打开 Vue
react 的数据管理方案:redux 还是 mobx?
IMWeb前端团队
2018/01/08
2.1K0
react 的数据管理方案:redux 还是 mobx?
JS前端技术类文章
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
达达前端
2021/10/28
4.3K0
用MobX管理状态(ES5实例描述)-2.可观察的类型
用observable.shallowObject(value)方法可以实现“浅观察”,只自动响应“浅层”的子属性
江米小枣
2020/06/15
7410
Mobx的使用
@语法糖报错 解决办法安装下面两个命令 yarn add @babel/plugin-proposal-decorators yarn add @babel/plugin-proposal-class-properties
世间万物皆对象
2024/03/20
1920
《The Joy of Javascript》- 4 - Meta Programming
使用相同的值重新定义 symbol 将会得到两个完全不同的示例 Symbol('a') !== Symbol('a')
szhshp
2022/09/21
2140
redux源码解析
applyMiddleware.js import compose from './compose' /** * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store. This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner,
theanarkh
2019/03/06
1.2K0
Decorator 从原理到实践
ES6 已经不必在过多介绍,在 ES6 之前,装饰器可能并没有那么重要,因为你只需要加一层 wrapper 就好了,但是现在,由于语法糖 class 的出现,当我们想要去在多个类之间共享或者扩展一些方法的时候,代码会变得错综复杂,难以维护,而这,也正式我们 Decorator 的用武之地。
Nealyang
2019/09/29
5470
Decorator 从原理到实践
Decorator 装饰器
大家在前端开发过程中有遇到过 @ + 方法名 这种写法吗?当我第一次看到的时候,直接懵了,这是什么东东……
政采云前端团队
2022/03/29
4200
Decorator 装饰器
【MobX】MobX 简单入门教程
<img src="http://images.pingan8787.com/blog/mobx.png" width="120px"/>
pingan8787
2019/10/24
1.5K0
相关推荐
各流派 React 状态管理对比和原理实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档