mobx教程(二)-mobx主要概念

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/smk108/article/details/84960159

通过《Mobx教程(一)-Mobx简介》我们简单理解了Mobx的基本原理及流程,使用Mobx实现一个响应式的应用,主要分为三个步骤:

  1. 定义状态并使其可观察(state)
  2. 对状态改变的响应(computed、autorun、reaction、observer、when)
  3. 改变状态(action)

下面从这三个步骤的顺序介绍Mobx的主要概念。(版本为5.X、使用es7装饰器)

1、可观察状态

状态是驱动应用的数据,是应用的核心。状态可以是与应用相关的各种数据,通常有像待办事项列表这样的领域特定状态,还有像当前已选元素的视图状态,当然,你也可以向Redux类似,将state分为三类:与领域直接相关的领域状态数据,反应用户行为的应用状态数据、视图相关的UI状态数据。使用Mobx一般还会创建一个或多个store用来管理state。

Mobx提供了observable和@observable两个API用来定义可观察的state。

  • observable(value)
  • @observable classProperty = value

可以使用这两个API定义可观察state的数据类型可以是JS基本数据类型、引用类型、普通对象、类实例、数组和映射。observable对不同类型有不同的转换规则,详细解释如下:

注:下文提到的会触发更新都是指受其影响的computed value(被依赖)和reaction都会自动更新。

(1)JS基本数据类型

JavaScript 中的所有原始类型值都是不可变的,因此值并不会是可观察的,Mobx会将包含值的属性转换成可观察的。可以如下定义:

@observable count = 1;
@observable city = 'shanghai';

当再改变count和city的值时,会触发更新。

(2)Array

如果 value 是数组,会返回一个 Observable Array。

@observable list = ['a', 'b', 'c', 'd'];

对于对象数组,observable也会递归地作用于数组的每个元素对象,其元素对象的属性也会是可观察的。

@observable todos = [
    {'id': 1, 'taskName': 'task1', 'finished': true},
    {'id': 2, 'taskName': 'task2', 'finished': false},
    {'id': 3, 'taskName': 'task3', 'finished': true},
    {'id': 4, 'taskName': 'task4', 'finished': false},
    {'id': 5, 'taskName': 'task5', 'finished': true},
    {'id': 6, 'taskName': 'task6', 'finished': false},
];

当数组元素的增加、减少或者元素对象的属性改变时,会触发更新。

(3)普通对象

对于没有原型或原型是Object.prototype的普通对象,那么对象会被克隆并且所有的属性都会被转换成可观察的。

{'id': 1, 'taskName': 'task1', 'finished': true}

Id、taskName、finished都会是可观察的,这些属性值的改变都会触发更新。

如果属性的值是一个普通对象,会继续被observable处理,将其属性转换为可观察的,一直如此递归执行。

'id': 1, 'taskName': 'task1', 'finished': {'part1': true, 'part2':false}}

part1和part2也会是可观察的,其值的修改依然会触发更新。

如果以后给可观察属性再赋值是一个普通对象时,新的普通对象值的属性也将被转换为可观察的。

若一个可观察对象的值为:

{'id': 1, 'taskName': 'task1', 'finished': true}

之后被修改为

{'id': 1, 'taskName': 'task1', 'finished': {'part1': true, 'part2':false}}

part1和part2依然是可观察的,其值的修改依然会触发更新。

对于5.x版本,以后新增加的属性也会是可观察的,4.x及以下版本是不可观察的。

若一个可对象的值为:

{'id': 1, 'taskName': 'task1', 'finished': true

后面被追加一个属性

{'id': 1, 'taskName': 'task1', 'finished': true, owner: admin}

后添加的属性owner在5.x版本依然是可观察的。

我测试中发现删除对象的属性是不会触发更新的。

(4)Map

对于ES6的Map : 会返回一个新的 Observable Map。Map对象的每一个元素都是可观察的,并且向Map对象中添加和删除新的元素也是可观察的。

@observable task = new Map([['taskName', 'tank1'],['finished', true]]);

再执行

this.task.set('owner', 'admin');

owner也是可观察的,修改会触发更新。

(5)非普通对象

非普通对象是指构造函数为自定义函数的对象,非普通对象的属性不会受observable的影响而转换为可观察的,如果需要将非普通对象的属性转换为可观察的,需要在其自定义的构造函数中进行处理。例如:

class User{
    @observable name;
    @observable role;
    constructor(name, role){
        this.name = name;
        this.role = role;
    }
}
user = new User('admin', '管理员');

之后在对user实例的name或role进行修改,是会触发更新的。

注:对js基本数据类型和非普通对象,observable返回的其实是一个特殊的boxed values类型的可观察对象,保存的是一个指向原基本数据类型或对象的引用,这个引用是可观察的。

2、对状态改变的响应

本段介绍的是Mobx中的衍生(derivation),derivation是指可以从state中衍生出来的任何东西,可以是值(computed value)或者动作(autorun、reaction等)。Derivation可以自动响应state的变化而进行更新或执行有副作用的函数。

(1)Computed

计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值,应该由纯函数产生,计算值可以被其它 observer 使用,例如被ui使用。

Mobx提供了computed和@computed用于定义计算值:

attr = computed(() => value);
@computed get attr(){
    return value;
}

用法如下:

@observable list = ['a', 'b', 'c', 'd'];
@computed get listLength(){
    return this.list.length;
}
  • 计算值依赖的observable没有改变,计算值不会更新
  • 计算值采用延迟更新策略,当有其它计算值或reaction在使用时,才会计算
  • 计算值不在被使用时,默认会被回收,也不再更新
  • 计算值可以帮助实现实际可修改的状态尽可能的小,又有上述优化,可以尽量多的使用

(2)autorun

当你想创建一个永远不会被观察的响应式函数时,可以使用autorun。

disposer = autorun(() => {sideEffect});

当autorun依赖的状态变化时会自动执行参数的function,返回值disposer是autorun的清除函数,当不再需要autorun时,可以调用disposer清除autorun。

autorun参数中的函数在使用autorun时会立即执行一次,然后在每次它依赖的state发生变化时自动执行一次。

用法举例:

disposer = autorun(() => {
    console.log(`autorun : Now the active ID is ${this.activeItem }`);
});
  • autorun不会产生新值,是基于执行函数产生效果
  • autorun不会被观察,是反应式代码桥接到命令式代码的方式
  • 可用于打印日志、更新UI的代码、发起请求
    • autorun第一个参数是函数,可接受第二个参数,处理delay、name、error等

(3)reaction

reaction是autorun的变种,第一个参数为数据函数,第二个参数为效果函数,数据函数返回一个值,作为效果函数的参数,效果函数不会对依赖的状态作出反应,只有数据函数返回的值变化时才会执行。

reaction = reaction(() => data, (data, reaction) => { sideEffect })

用法举例:

@observable todos = [
    {'id': 1, 'taskName': 'task1', 'finished': true},
    {'id': 2, 'taskName': 'task2', 'finished': false},
    {'id': 3, 'taskName': 'task3', 'finished': true},
    {'id': 4, 'taskName': 'task4', 'finished': false},
    {'id': 5, 'taskName': 'task5', 'finished': true},
    {'id': 6, 'taskName': 'task6', 'finished': false},
];
reaction = reaction(() => this.todos.filter(item => item.finished === false), notFinished => {
    if(notFinished.length === 0){
        console.log('All tasks have been completed, and autorun and reaction are no longer executed.');
        this.disposer();
        this.reaction();
    }else {
        console.log(`reaction : Now the active ID is ${this.activeItem}`);
    }
});

在上面的例子中,reaction的第一个参数会为第二个参数提供notFinished,然后第二个参数是效果函数,会执行打印日志的操作,当todo都已经完成时,会调用disposer和reaction清除auto和reaction。

  • reaction 返回一个清理函数,不需要再执行时应该调用清理函数
  • 数据函数对state的改变作出反应
  • 效果函数仅对数据函数中访问的数据作出反应,不对state的改变作出反应
  • autorun第一个参数是函数,可接受第二个参数,处理delay、name、error等

(4)observer(其实也是autorun)

observer 函数/装饰器可以用来将 React 组件转变成响应式组件,observer 是由单独的 mobx-react 包提供。

mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。

@observer class MyComponent extends React.Component{...}

用法举例:

import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';
import {Button} from 'antd';
import Timer from '../Timer';
import './style.css';

@inject( 'userStore')
@observer
export default class User extends Component{
    constructor(props){
        super(props);
        this.state = {};
    }

    render(){
        const {user} = this.props.userStore;
        return(
            <div className='user'>
                <div className='user_list'>name:{user.name}</div>
                <div className='user_list'>role:{user.name}</div>
                <div className='user_list'>{user.isGuest ? `isGuest:${user.isGuest}` : ''}</div>
                <Button type='primary' onClick={() => this.props.userStore.changeUser()}>Change User</Button>
                <Timer />
            </div>
        );
    }
}
  • observer 需要组合其它装饰器或高阶组件时, observer需要是是最深处(第一个应用)的装饰器,否则可能不能正常更新。
  • 响应式组件没有或很少有状态,因为在与其他组件共享的对象中封装(视图)状态通常更方便。但你仍然可以自由地使用状态。
  • @observer 以和 PureComponent 同样的方式实现了 shouldComponentUpdate,因此子组件可以避免不必要的重新渲染。
  • 响应式组件单方面加载数据,即使子组件要重新渲染,父组件也不会进行不必要地重新渲染。

(5)when

when(() => condition, () => {sideEffect})

when会自动响应它使用state的变化,也就是会观察并运行第一个参数,直到condition返回true。 一旦返回 true,给定的sideEffect就会被执行,且只会执行一次,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。

用法举例:

constructor(){
    when(() => this.hasNotFinished.length === 0, () => {
        this.disposer();
        this.reaction();
    });
}
@computed get hasNotFinished(){
    return this.todos.filter(item => item.finished === false);
}
  • when非常适合用在以响应式的方式执行取消或清除逻辑的场景
  • when最多执行一次
    • when返回一个清理器,可以在需要时提前清除

(6)mobx对什么作出反应

MobX 会对在追踪函数执行过程读取现存的可观察属性做出反应。

  • “读取” 是对象属性的间接引用,可以用过 . (例如 user.name) 或者 [] (例如 user['name']) 的形式完成。
  • “追踪函数” 是 computed 表达式、observer 组件的 render() 方法和 when、reaction 和 autorun 的第一个入参函数。
  • “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。

MobX 追踪属性访问,而不是值。

Mobx不会对可观察状态的改变作出反应举例:

@observable message = {
    title: "Foo",
    author: {
        name: "Michel"
    },
    likes: [
        "John", "Sara"
    ]
}

如上message对应的可观察图示:

  • 在追踪函数外进行间接引用
ar title = message.title;
autorun(() => {
    console.log(title)
})
message.title = "Bar"
  •    MobX 只追踪同步地访问数据
autorun(() => {
    setTimeout(
        () => console.log(message.likes.join(", ")),
        10
    )
})
message.likes.push("Jennifer");

 在 autorun 执行期间没有访问到任何 observable,而只在 setTimeout 执行期间访问了。

  • MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪

React中使用mobx,只会对render中直接存取的可观察状态进行跟踪

像上述将值传递给子组件和将可观察状态值缓存到组件内是不会对状态的变化有反应的。

3、改变状态

动作是任何用来修改状态的函数,且应该永远只对修改状态的函数使用动作。

mobx中可以在任意位置修改状态,但是推荐使严格模式,在严格模式下,只能在action中修改状态。

Mobx提供action和@action包装action函数

Mobx提供action.bound和@action.bound帮助bind this

@action classMethod() {}
@action(name) classMethod () {}
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}

用法举例:

class commonStoreClass {
    @observable time = '';
    @action updateTime(time){
        this.time = time;
    }
    @action.bound computedTime(){
        const nowTime=new Date();
        const year=nowTime.getFullYear();
        const month=nowTime.getMonth()+1;
        const date=nowTime.getDate();
        const time = year+"年"+month+"月"+date+" "+nowTime.toLocaleTimeString();
        this.updateTime(time);
    }
    @action startTime(){
        this.timer = setInterval(this.computedTime,1000);
    }
    @action stopTime(){
        clearInterval(this.timer);
    }
}
  • 函数内多个修改状态的操作会被合并,全部修改完后会通知computed和reaction
  • 应该永远只对修改状态的函数使用动作。 只执行查找,过滤器等函数应该被标记为动作
  • 推荐使严格模式,在严格模式下,只能在action中修改状态。
  • 注意在需要时bind this

4、各api的import

上面提到的api中与React相关的由mobx-react提供,其余的由mobx包提供。

import {observable, action, computed, autorun, reaction, when} from 'mobx';
import {Provider, inject, observer} from 'mobx-react';

Provider与inject的使用会在后面介绍。

猜你喜欢

转载自blog.csdn.net/smk108/article/details/84960159