Mobx 入门

本文探讨了Mobx与Redux在开发复杂性、代码量和性能上的差异,指出Mobx由于其响应式核心思想和简单的API,提供了更轻松的开发体验和高效的渲染性能。文章详细介绍了Mobx的基础,如decorator装饰器,以及observable、computed、autorun、when和reaction等关键API的使用方法。同时,还分享了最佳实践以提升应用性能,包括如何避免不必要的重渲染和优化数据访问策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Mobx 和 Redux 的关系

开发难度低

mobx 使用响应式的风格,对于面向对象响应式的编程风格,匹配更加简略的API语法,降低了学习成本,mobx 的集成度比 redux 要高。多在 class 中书写变量,不像 redux 会在统一的文件中书写。

开发代码量少

mobx 中的数据只需要在 store 中更新即可,大大优于 redux 需要创建新的 state 的更新方式。

渲染性能好

mobx 不存在 SCU 的问题,因为它可以精确表示哪些组件是可以重渲染的,哪些不需要重渲染,通过合理的组建组件层级和组件位置,可以将组件的重渲染限制在很小的范围之内,从而影响页面性能。

mobx的核心思想

状态变化引起的副作用应该被自动触发
应用逻辑只需要修改状态数据即可,mobx 会自动触发写缓存,渲染UI等,无需人工干预

控制流:
单向数据流模式
在这里插入图片描述

Mobx所需基础

  • Class 类:继承(extends),多态
  • Decorator 装饰器
    • decorator是在声明阶段实现类与类成员注解的一种语法

decorator 装饰器简单理解

decorator 简单来说就是一个函数,是用来规定类或者类成员的一种手段
比如实现一个 readonly 的装饰器,来规定 类成员只能被读不能被修改

// readonly有三个参数
// 分别为: target类的实例对象,key类成员的名称,descriptor为类成员的描述符
function readonly(target, key, descriptor) {
    descriptor.writable = false;
}

然后定义一个类 Numberic
将 readonly 来修饰类 Numberic 中的 PI,使其不能被修改

class Numberic {
    @readonly PI = 3.14;
    // add 方法不重要,可忽略
    // 定义一个add方法,可以返回任意输入个数的数字并返回他们的和
    add(...nums) {
        return nums.reduce((p, n) => (p + n), 0)
    }
}

然后新建一个 Numberic 的实例并尝试修改 PI 的值

new Numberic().PI = 111

发现会报错
在这里插入图片描述
完整代码

// 三个参数 target类的实例对象,key类成员的名称(PI),descriptor为类成员的描述符
function readonly(target, key, descriptor) {
    descriptor.writable = false;
}

// log 在每个Numberic函数的调用前后打印一条信息
@log
class Numberic {
    @readonly PI = 3.14;
    // 定义一个add方法,可以返回任意输入个数的数字并返回他们的和
    add(...nums) {
        return nums.reduce((p, n) => (p + n), 0)
    }
}

new Numberic().PI = 111

Mobx 常用 API

可观察的数据(observable)

observable 是一种让数据的变化可以被观察的方法

哪些数据可以被观察呢?

理论上所有数据都可以被观察,但是我们最常用的就是以下几种:

  • 原始类型:String,Number,Boolean,Symbol
  • 对象
  • 数组

从 mobx 中引入 observable

建议使用 webpack 搭建环境,使用 npm 包安装mobx

import {obeservable} from 'mobx'

对于上面讲到的可观察的数据,需要做下分类:

  • 对于数组纯对象Map,直接把 observable 当做函数,把变量转换为可观察的对象
  • 对于上面没有包含的其他类型,必须调用 observable.box 来将变量包装为可观察的对象,之后对该变量的直接赋值将会被监视
对于数组,纯对象和Map——可直接观察
// array object map

// array
const arr = observable(['a', 'b', 'c'])
// object
const obj = observable({
    a: 1,
    b: 2
})
console.log(obj.a);
// proxy 可以监视新增加的数据
obj.c = 3;
console.log(obj.c); // 3

// map 
const map = observable(new Map())

map.set('a', 1)

console.log(map.has('a')); // true

对于其他类型——可间接观察
// 对于其他类型虽然没有监控,但可以对变量本身监控
const num = observable.box(20);
const str = observable.box('hello');
const bool = observable.box(true);

// get 方法可以返回原始类型值
console.log(num.get(), str, bool);
// set 方法可以修改原始类型值
num.set(50)
console.log(num.get());
结合 decorator 修饰器

若使用 decorator 修饰器可以自动识别类型,不用手动调用.box来区分直接还是间接观察数据

class Store {
    @observable array = [];
    @observable obj = {};
    @observable map = new Map();
    // 会自动识别,不用手动区分可观察还是间接观察
    @observable string = 'hello';
    @observable number = 20;
    @observable bool = false;
}

mobx常用API

对可观察的数据做出反应

观察数据变化的方式

  • computed
  • autorun
  • when
  • reaction
computed
  1. computed 可以将其他可观察数据以用户想要的方式组合起来变成一个新的可观察数据
  2. 也可以结合observe使用,来修饰类的对象成员
// 先定义一个 Store ,里面存放两个不同类型的数据
// 并且使用 @observable 观察起来
class Store {
    @observable string = 'hello';
    @observable number = 20;
}
// 定义一个新的 Store
const store = new Store();

// 使用 computed 方法返回一个新的组合变量:computedString
const computedString = computed(function () {
    return store.string + '/' + store.number + '/'
})
// 方式1: 直接使用 .get 方法获得新的对象值
console.log(computedString); // ComputedValue 对象     
console.log(computedString.get()); // hello/20/
// 方式2:使用 observe 方法来观察实时变化

// 为了观察到 computedString 的变化,需要使用 observe 方法
// 需要函数入参,change存储了修改前后变量的值
computedString.observe(function (change) {
    console.log(change.newValue);
})

store.string = 'world'
// string发生了变化:world/20/
store.number = 30
// number发生了变化:world/30/
autorun

autorun自动运行传到autorun的函数

// autorun
// autorun自动运行传到autorun的函数
autorun(() => {
    console.log(store.string + '/' + store.number + '/');
    // 自动会执行里面的内容
})
// 修改 autorun 中的任意可观察数据可以自动运行 autorun 中的内容
// 总共打印三次:hello/20/ world/20/ world/30/
store.string = 'world'
// string发生了变化:world/20/
store.number = 30
// number发生了变化:world/30/
when

用法:接收两个函数参数
第一个函数参数根据可观察函数参数返回的布尔值来决定第二个函数是否执行,并执行一次

  • 必须根据可观察数据来判断,不能引用其他变量
  • 如果一开始里面就是真的话,就会同步执行代码
// when
// 用法:接收两个函数参数,第一个函数参数根据可观察函数参数返回的布尔值来决定第二个函数是否执行,并执行一次
when(() => store.bool, () => console.log("it's true"))

store.bool = true
// 因此将bool修改为 true 后,才打印 "it's true"
// 
// 注意1:必须根据可观察数据来判断,不能引用其他变量
// 下面两行不会打印
when(() => !!store.bar, () => console.log("it's true"))

store.bar = true
// 
// 注意2:如果一开始里面就是真的话,就会同步执行代码
// 此时 store.bool 为 true
console.log('before');
when(() => store.bool, () => console.log("it's true"))
console.log('after');
// 以此打印 before it's true after
reaction

接收两个函数类型的参数,第一个函数也用可观察数据并返回一个值,这个值会作为第二个值的参数

// reaction
// 用法:接收两个函数类型的参数,第一个函数也用可观察数据并返回一个值,这个值会作为第二个值的参数
reaction(() => [store.string, store.number], arr => console.log(arr.join('/')))
store.string = 'world'
// string发生了变化:world/20/
store.number = 30
// number发生了变化:world/30/
// 使用场景:在没有数据之前,我们不想也没有必要调用写缓存的逻辑,可以用reaction实现在数据被第一次填充之后才启动写缓存逻辑
  • 直接修改可观察数据的值方式是通过变量赋值的方式实现的
  • 虽然简单易懂但是会带来一个较为严重的副作用:每次修改都会触发 autorun,reaction运行一次
  • 在多数情况下,高频触发是完全没有必要的
    • 比如用户对视图的一次点击操作
    • 变量改了很多,但是视图更新很可能只需要一次
class Store {
...
    @action bar() {
        this.string = 'world';
        this.number = 30;
    }
}
reaction(() => [store.string, store.number], arr => console.log(arr.join('/')))

store.bar(); // world/30
// 只有了一次的操作

// 或者可以使用 action.bound
// 将上面的 @action bar() 改为 @action.bound bar()
// 就可以下述这种操作
// const bar = store.bar;
// bar()

runInAction(() => {
    store.string = 'world';
    store.number = 30;
})
// runInAction 和调用 store.bar()效果相同
// 也可以加入一个修饰符,对于调试比较友好
runInAction('modify', () => {
    store.string = 'world';
    store.number = 30;
})

action被定义为任何修改状态的行为
使用action的收益:多次对数据状态的赋值合并成一次,从而减少autorun和reaction等的次数
action既可以作为普通函数也可以作为decorator来使用

总结

computed 可将多个可观察数据组合成一个可观察数据
autorun 可以自动追踪引用的观察数据,并在数据变化时重新触发
when 提供了条件执行逻辑,autorun的一个变种
reaction 可以分离可观察数据声明,以副作用的方式对 autorun 做出改进

常用工具函数

  • observe
  • toJS
  • trace
  • spy

observe

不要与 observable,observer 混淆,它是一个纯函数(observable观察数据,observer观察函数),不能用作监视器,是用来监视的,observable和 action 的结合体
在 store 中写一个 observe
来监视todos

class TODOS {
	@observable todos = [];
	constructor(){
	        observe(this.todos,change=>{
	            console.log(change)
	        })
	    }
  	}

当 todos 这个数组发生变化后,都会打印一条结果出来
在这里插入图片描述

spy

spy 可以全局观察到内容

spy(event=>{
    console.log(event)
})

trace

trace 可以打断点
在每个 render 后 都可以 trace
可以发现一些组件的优化点
在这里插入图片描述

toJS

toJS(value, options?)
递归地将一个(observable)对象转换为 javascript 结构

最佳实践-提升性能

提升性能的三大法则:
法则1:尽可能晚的取出可观察数据的值
法则2:用单独的组件映射集合
法则3:使用专用组件处理列表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值