转载

MobX入坑指南(4) -- Utility functions

之前几篇大概介绍了mobx最常用的几个方法,这次准备把剩余的公共方法都介绍了。

autorunAsync

autorunAsync(debugName?: string, action: () => void, minimumDelay?: number, scope?): disposer

autorunAsync 的功能与 autorun 相似,功能都是在观测对象发生变化时自动运行回调函数 action 。不同点在于 autorun 是在观测对象发生变化时立即执行的,而 autorunAsync 是异步的,可以通过 minimumDelay 参数来指定延迟的时间。

如果被观测对象的在延迟过程中发生多次变化, action 也只会在延迟结束时触发一次,所以它和后面要介绍到的 transaction 方法效果类似。在某些场景下这个方法很有用,比如他可以被用来防止频繁向服务端发起请求。

如果传了 scope 参数,那么 scope 将作为 action 运行时的this。

如果传了第一个参数 debugName ,那么在调试工具中将使用 debugName 作为调试信息。

autorun 一样, autorunAsync 也会返回一个销毁函数。

autorunAsync(()=> {
    // 我们假设 searchBar.keyword 已经被观测, 是搜索输入框的值。当它发生变化时我们要把它发送到服务端请求搜索结果。
	// 如果这里使用autorun,那么每次变化都会向调用sendKeywordToServer。
    // 使用autorunAsync延迟300ms发送,当发送时,searchBar.keyword会是这300ms内变化的最终值。
    // 这样就可以有效的防止频繁请求造成服务抖动。
    sendKeywordToServer(searchBar.keyword);
}, 300);

Atom类 和 Reaction类

Atom

有些时候,你可能想要有更多的数据结构或其他的东西(比如streams),也可以用于响应计算。可以使用 Atom 类简单快速的实现这一功能。 Atom 实例可以通知mobx观测对象发生了变化,而mobx会在启用和停用观测对象的时候通知 Atom 实例。

下面的例子展示了 Atom 的全部功能,这个例子展示了如何创建一个时钟,这个时钟只有在被观测时才会运行。

import {Atom, autorun} from "mobx";

classClock{
    atom;
    intervalHandler = null;
    currentDateTime;

    constructor() {
        // 创建一个Atom实例
        this.atom = new Atom(
            // 第一个参数: Atom实例的名字, 调试用的
            "Clock",
            // 第二个参数(可选): 从不被监听到被监听时的回调函数.
            () => this.startTicking(),
            // 第三个参数(可选): 从被监听到不被监听时的回调函数
            // 注意,atom实例会多次在这两种状态见转换
            () => this.stopTicking()
        );
    }

    getTime() {
        // 如果Atom实例被响应函数调用,则reportObserved返回true。
		// 同时,reportObserved会通知mobx这个实例在响应回调中被使用了,它还会触发实例的第二个参数(startTicking)
        if (this.atom.reportObserved()) {
            return this.currentDateTime;
        } else {
            // 当没有响应函数调用Atom实例的时候,就不会触发startTicking。
            // 根据不同的情况,这里也可以做不同的处理,比如抛出一个错误,返回一个默认值等等。
            return new Date();
        }
    }

    tick() {
        this.currentDateTime = new Date();
		// 通知mobx当前值发生了变化
        this.atom.reportChanged();
    }

    startTicking() {
        this.tick(); // 初始化时钟
        this.intervalHandler = setInterval(
            ()=> this.tick(),
            1000
        );
    }

    stopTicking() {
        clearInterval(this.intervalHandler);
        this.intervalHandler = null;
    }
}

const clock = new Clock();

const disposer = autorun(()=> console.log(clock.getTime()));

// ... 每秒打印时间

disposer();

// 停止打印。如果没有响应函数调用当前clock实例,那么时钟将停止。会触发stopTicking函数。

Reaction

使用 Reaction 可以创建一个自定义的监听器。 Reaction 接受一个函数作为参数,他会分析这个函数所依赖的被观测对象,然后追踪他们,当他们发生变化时发出事件。

下面的例子展示了 autorun 是如何用 Reaction 实现的,其实这个例子我没看太懂,貌似必须调用 Reaction 的track方法才能追踪并发出信号,但是例子中是在 Reaction 接收的函数中调用,然后runReaction的时候开始,具体的得等我翻了源码之后才能知道了……

export functionautorun(view: Lambda, scope?: any){
    if (scope)
        view = view.bind(scope);

    const reaction = new Reaction(view.name || "Autorun", function(){
        this.track(view);
    });

    // 开始或者排入队列
    if (isComputingDerivation() || globalState.inTransaction > 0)
        globalState.pendingReactions.push(reaction);
    else
        reaction.runReaction();

    return reaction.getDisposer();
}

expr

expr(worker: () => void)

expr 可以在计算属性的函数中创建一个临时的计算属性,其实就是 computed(func).get() 。作者在文档中说设计这个api的意图是为了提升计算属性的性能,比如下面的例子,如果使用 expr 替代直接用比较运算,可以利用计算属性的缓存,减少运算次数。

const TodoView = observer(({todo, editorState}) => {
    const isSelected = mobx.expr(()=> editorState.selection === todo);
    return <div className={isSelected ? "todo todo-selected" : "todo"}>{todo.title}</div>;
});

extendObservable

extendObservable(target: object, ...properties: object)

在之前的几篇文章中,我们已经大概见过 extendObservable 应用的实例了。 extendObservableObject.assign 类似,接受多个参数,将 properties 上所有的键值对,都合并到 target 上,同时把它们都转换成可观测的属性。

如果属性值是一个没有参数的函数,那 extendObservable 将用 computed 把它转化为一个计算属性。

所以, observable(object) 其实是 extendObservable(object, object) 的别名。

var Person = function(firstName, lastName){
    // 在当前实例为观测对象
    extendObservable(this, {
        firstName: firstName,
        lastName: lastName
    });
}

var matthew = new Person("Zheng", "Xingcheng");

// 向观测对象上添加属性
extendObservable(matthew, {
    age: 30
});

isObservable

isObservable(testValue:object, propertyName?: string)

isObservable 是用来判断一个变量是不是用observable观测对象的,如果是就会返回true,如果想看变量的某个属性是否可观测,直接传入属性的引用是不行的,需要传第二个参数 propertyName 指定要判断哪个属性,如果属性可观测,就返回true

var person = observable({
    firstName: "Zheng",
    lastName: "Xingcheng"
});

person.age = 30;

console.log(isObservable(person)); // true
console.log(isObservable(person, "firstName")); // true
console.log(isObservable(person.firstName)); // false (just a string)
console.log(isObservable(person, "age")); // false

为了细化各种类型的判断,mobx还提供了map,array,object三种类型的判断,比起 isObservable ,他们的判断标准更严格,如果类型不符合就会返回false。

isObservableMap

isObservableMap(testValue:object)

如果 testValue 是用 mobx.map 创建的对象,则返回true。

isObservableArray

isObservableArray(testValue:object)

如果 testValue 是用 mobx.observable(array) 创建的对象,则返回true。

isObservableObject

isObservableObject(testValue:object)

如果 testValue 是用 mobx.observable(object) 创建的对象,则返回true。

var testValue = observable({
	arr: [1, 2, 3],
    obj: {
    	x: 1
    },
    map: map([['y',2]])
});
console.log(isObservableMap(testValue.map)); // true
console.log(isObservableArray(testValue.arr)); // true
console.log(isObservableObject(testValue.obj)); // true

spy

spy(listener)

spy 可以注册一个全局的监听函数,监听所有的mobx发出的事件,通常是用来做log或者做调试的。

比如以下例子,会打印所有的action:

spy((event) => {
    if (event.type === 'action') {
        console.log(`${event.name}with args:${event.arguments}`)
    }
})

不同的操作,event也会不一样,下面的表格是每种事件对应的参数:

event event带的属性 是否可以嵌套发生
action name, target (scope), arguments, fn (source function of the action) yes
transaction name, target (scope) yes
scheduled-reaction object (Reaction instance) no
reaction object (Reaction instance), fn (source of the reaction) yes
compute object (ComputedValue instance), target (scope), fn (source) no
error message no
update (array) object (the array), index, newValue, oldValue yes
update (map) object (observable map instance), name, newValue, oldValue yes
update (object) object (instance), name, newValue, oldValue yes
splice (array) object (the array), index, added, removed, addedCount, removedCount yes
add (map) object, name, newValue yes
add (object) object, name, newValue yes
delete (map) object, name, oldValue yes
create (boxed observable) object (ObservableValue instance), newValue yes

toJS

toJS(value: any, supportCycles?=true: boolean)

toJS可以将一个observableObject下的转化为javascript原生的对象。他会递归转换array,object,map和基础类型的值,但是不会转换计算属性和其他不可枚举的值。默认情况下,toJS会缓存下每次运行的值,貌似作者设计这个api就是为了输出log用的,可以设置 supportCycles 参数为false来提高toJS的性能。

对于更复杂的序列化反序列化场景,mobx的作者推荐使用他开发的 serializr 库。

transaction

transaction(worker: () =&gt; void)

在之前的 autorunAsync 有提到过,除了 autorunAsync ,还可以使用 transaction 来做批量处理。

transaction 用来批处理一系列的更新,而不会通知观测对象,当所有更新结束,才会发出通知。 transaction 接收一个没有参数的worker函数作为参数,在这个函数执行完成之前,不会通知观察者。 transaction 的返回值就是worker函数的返回值。另外 transaction 是同步的,可以被嵌套,只有最外层的 transaction 执行完,才会触发响应。

import {observable, transaction, autorun} from "mobx";

const numbers = observable([]);

autorun(()=> console.log(numbers.length, "numbers!"));
// Prints: '0 numbers!'

transaction(()=> {
    transaction(()=> {
        numbers.push(1);
        numbers.push(2);
    });
    numbers.push(3);
});
// Prints: '3 numbers!'

untracked

untracked(fn: () =&gt; void)

使用 untracked 可以创建一个不被观测的代码块,通常 untracked 需要放在 (@)action 里面才有意义,比如以下的例子:

const person = observable({
    firstName: "Michel",
    lastName: "Weststrate"
});

autorun(()=> {
    console.log(
        person.lastName,
        ",",
        // person.firstName放在了untracked的回调里面,所以不会跟这个autorun的监听函数绑定到一起
		// 在修改person.firstName时就不会触发这个监听函数
        untracked(()=> person.firstName)
    );
});
// prints: Weststrate, Michel

person.firstName = "G.K.";
// doesn't print!

person.lastName = "Chesterton";
// prints: Chesterton, G.K.

when

when(debugName?, predicate: () =&gt; boolean, effect: () =&gt; void, scope?)

when 会感测并运行参数predicate,predicate有点类似一个计算属性,当predicate为true的时候,则自动运行effect,然后销毁自己。所以 when 是一个只运行一次的 autorun

下面这个例子展示了用 when 来实现自动销毁组件的功能:

classMyResource{
    constructor() {
        when(
            // 当断言为真...
            () => !this.isVisible,
            // ... 则运行一次然后销毁
            () => this.dispose()
        );
    }

    @computed get isVisible() {
        // 返回组件是否可见
    }

    dispose() {
        // 销毁组件
    }
}

总结

本篇整理了下mobx的公共方法和作用。就此,基础api算是都介绍完了。之后会再着重写些使用方法和介绍mobx原理的内容。

话说最近懒癌又开始发作了……_(:3 」∠)_……看着朋友们跟打了鸡血一样写那么多blog好着急的说……希望以后能迎头赶上吧……

原文  http://brooch.me/2016/12/16/MobX-simple-entry-4/
正文到此结束
Loading...