用MobX管理状态(ES5实例描述)-2.可观察的类型

1,857 阅读5分钟

MobX是一个简单有效的状态管理库,以派生(derive)的概念为核心,以观察者模式为手段,达到了修改数据自动更新界面等目的

  • 正因为其本身提供了包装react的方法,可以简洁的改善react组件,所以官网文档和几乎所有教程都以react和ES7的装饰修饰符等特性为切入点

  • 但MobX在传统的ES5环境中也能良好工作,本文尝试以此为出发点,探讨在既有的非react项目中直接引入MobX并用其整理重构老代码的方法

没有babel、webpack、JSX...那么多的套路!直接上手先,走你~

[II]. 可观察的类型

语法 mobx.observable(value)

2.1 普通对象

  • 普通对象指不是通过构造函数创建的,没有特定原型对象的 plain object

  • 如果一个普通对象被传递到 observable() 中,其所有属性都会成为可观察的,并被拷贝到一个副本中(对副本的更改也同时影响原始对象的值)

  • 默认是递归处理的,如果一个属性是对象或数组,其元素也会被观察

var $ctn = document.querySelector('#container');

var obj = {
    a: 1,
    b: {
        c: 3,
        d: 4
    }
};

var observ = mobx.observable(obj);
    
setTimeout(function(){
    observ.b.c = 666;
}, 1000);

mobx.autorun(function(){
    //元素内容变为 [new value] c: 666
    $ctn.innerHTML = "[new value] c: " + observ.b.c;
});

setTimeout(function(){
    alert([obj.b.c, observ.b.c]; //666, 666
}, 2000);

浅观察:

observable.shallowObject(value)方法可以实现“浅观察”,只自动响应“浅层”的子属性

var $ctn1 = document.querySelector('#container1');
var $ctn2 = document.querySelector('#container2');
var $info = document.querySelector('#info');

var obj = {
    a: 1,
    b: {
        c: 3,
        d: 4
    }
};

var observ1 = mobx.observable.shallowObject(obj);
var observ2 = mobx.observable.shallowObject(obj);

mobx.autorun(function(){
    //元素内容变为 [new value of observ1] c: 3
    $ctn1.innerHTML = "[new value of observ1] c: " + observ1.b.c;
});
mobx.autorun(function(){
    //元素内容变为 [new value of observ2] c: 999, a: 555
    $ctn2.innerHTML = "[new value of observ2] c: " + observ2.b.c + ", a: " + observ2.a;
});

setTimeout(function(){
    observ1.b.c = 666;
    observ2.b.c = 999;
    observ2.a = 555;
}, 1000);
setTimeout(function(){
    $info.innerHTML = [obj.b.c, observ1.b.c, observ2.b.c].join(','); //999, 999, 999
}, 2000);

2.2 数组

  • 和对象类似的是,向observable()传递一个数组参数,数组中的每一项也会变为可观察的,且默认为递归处理的深度观察

  • 和对象类似,数组也有一个浅观察的方法 observable.shallowArray(value)

  • Array.isArray(observable([]))会返回fasle,但可用 Array.isArray(observable([]).slice())达到正确的效果

  • 与原生数组对象的sort()reverse()方法不同的是,可观察数组的这两个方法返回相应结果的一个数组副本,而不影响原数组

  • 除了内建的数组方法,可观察数组也扩展了如下方法:

    • clear()

    • replace(newItems)

    • find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?)

    • remove(value)

    • peek(): 和slice()类似,返回一个安全的原生数组

    • intercept(change=> change|null ): 拦截更改,并可指定使用自定义后的更改

    • observe(change=>{}, fireImmediately? = false): 监听更改

var todos = observable([
    { title: "Spoil tea", completed: true },
    { title: "Make coffee", completed: false }
]);

autorun(() => {
    console.log("Remaining:", todos
        .filter(todo => !todo.completed)
        .map(todo => todo.title)
        .join(", ")
    );
});
// Prints: 'Remaining: Make coffee'

todos[0].completed = false;
// Prints: 'Remaining: Spoil tea, Make coffee'

todos[2] = { title: 'Take a nap', completed: false };
// Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'

todos.shift();
// Prints: 'Remaining: Make coffee, Take a nap'

2.3 Map

  • observable.map(values?) 可以创建一个可观察的Map类型

  • 可选的一个参数,可以是一个对象、一个ES6 Map,或是一个键值字符串数组

  • 类似于对象,可以用observable.shallowMap(values)实现浅观察

var a = mobx.observable.map({a:111})
console.log('map1 ', a.get('a')); //111

var es6Map = new Map();
es6Map.set('a', 222);
var b = mobx.observable.map(es6Map);
console.log('map2', b.get('a')); //222

var c = mobx.observable.map(['a', 'b', 'c']);
console.log(c.toJS()); //{a: undefined, b: undefined, c: undefined}

和ES6规范中相同的方法包括:

  • has(key)

  • set(key, value)

  • delete(key)

  • get(key)

  • keys()

  • values()

  • entries()

  • forEach(callback:(value, key, map) => void, thisArg?)

  • clear()

  • size

不同于ES6规范的方法包括:

  • toJS() - 得到一个浅复制的javascript对象( 深复制用mobx.toJS(map) )

  • merge(values) - 合并新的对象到Map中,参数格式同初始化方法

  • replace(values) - 替换,相当于 map.clear().merge(values)

  • intercept(interceptor)

  • observe(listener, fireImmediately?)

2.4 基本类型值和引用

  • 所有JS的基本值都是不可变的,因此单个变量无法被观察

  • MobX将这些类型转换成可观察的“boxed value”

转换后的对象可调用如下方法:

  • get() - 取得当前值

  • set(value) - 替换当前值,并通知所有观察者方法

  • intercept(interceptor)

  • observe(callback: (change) => void, fireImmediately = false)

const cityName = observable("Vienna");

console.log(cityName.get());
// prints 'Vienna'

cityName.observe(function(change) {
    console.log(change.oldValue, "->", change.newValue);
});

cityName.set("Amsterdam");
// prints 'Vienna -> Amsterdam'

浅观察

  • observable.shallowBox(value)基于 observable.ref()实现了浅观察

  • 这意味着只观察引用本身,而其值并不会被自动观察

var str2 = "world";
var pobj2 = mobx.observable(str2);

var str3 = "!!!";
var pobj3 = mobx.observable.shallowBox(str3);

mobx.autorun(function(){
    console.log(str2, pobj2.get());
    console.log(str3, pobj3.get());
});

setTimeout(function() {
    console.log('[after 1s]');
    mobx.runInAction(()=>{
        pobj2.set("wo____rld");
        pobj3.set("~~~");
    });
}, 1000);

setTimeout(function() {
    console.log('[after 2s]');
    mobx.runInAction(()=>{
        pobj2.set({a: 11});
        pobj3.set({b: 22});
    });
}, 2000);

/* autorun输出:

world world
!!! !!!

[after 1s]
world wo____rld
!!! ~~~

[after 2s]
world {$mobx: ObservableObjectAdministration}
!!! {b: 22}
*/

2.5 类实例

对于类实例,需要在构造函数中或对实例对象调用mobx.extendObservable(targetName, ...props)方法:

var Person = function(firstName, lastName) {
    //不需要观察的实例属性
    this.id = (new Date).getTime();
    //需要观察的实例属性
    mobx.extendObservable(this, {
        firstName: firstName,
        lastName: lastName
    });
}

var matthew = new Person("Matthew", "Henry");

//对已初始化的实例增添新的可观察属性
mobx.extendObservable(matthew, {
    age: 25
});

类实例中的描述符

  • 描述符被用来对指定的属性定义特殊的行为

  • 比如用observable.ref()来浅观察引用、用 computed()来声明一个派生属性,或用action()定义一个改变状态的动作

var Person2 = function(firstName, lastName) {
    this.id = (new Date).getTime();
    mobx.extendObservable(this, {
        firstName: mobx.observable.ref(firstName),
        lastName: mobx.observable.ref(lastName),
        fullName: mobx.computed(function() {
            return this.firstName + " " + this.lastName
        }),
        setLastName: mobx.action(function(name) {
            this.lastName = name;  
        })
    });
};

var p2 = new Person2('tom', 'jerry');
p2.setLastName('trump');
p2.firstName = 'donald';
console.log(p2.fullName); //用computed定义的派生属性用法上类似getter

类实例中的 getter/setter

  • 也可以用getter定义一个派生属性

  • 配对的setter是可选的,用来定义一个action; 且该方法不能直接操作派生属性,而是通过改变核心状态影响它

var Person3 = function(firstName, lastName) {
    this.id = (new Date).getTime();
    mobx.extendObservable(this, {
        firstName: firstName,
        lastName: lastName,
        get fullName() {
            return this.firstName + " " + this.lastName
        },
        set fullName(newValue) {
            var parts = newValue.split(" ")
            this.firstName = parts[0]
            this.lastName = parts[1]
        }
    });
};

var p3 = new Person3;
p3.fullName = "ivanka trump";
console.log(p3.fullName, p3.firstName);

类实例中的浅观察

  • extendShallowObservable(value)同样基于 observable.ref()实现了浅观察

  • observable.deep(prop)被用来对某个属性单独指定深观察


* 原创文章转载请注明出处

-------------------------------------

长按二维码关注我们的公众号哦: