Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mobx 快速剖析 #51

Open
lulujianglab opened this issue Feb 29, 2020 · 0 comments
Open

mobx 快速剖析 #51

lulujianglab opened this issue Feb 29, 2020 · 0 comments

Comments

@lulujianglab
Copy link
Owner

由于近期 react 项目的数据状态是用 mobx 来管理,所以为了尽快上手项目,花了点时间研究了下,效果显著,所以特意总结了以下内容,可以用来快速理解 mobx 和上手项目

mobx 介绍

首先我们还是来快速介绍下 mobx 以及为什么需要 mobx

mobx 是什么

mobx 是一个前端领域的 js 框架,官方定义为简单、可扩展的状态管理

我们知道对于项目应用来说,数据是必不可少的,数据变量会记录某个时刻的状态,从而影响应用下一个时刻的状态
特别是对于高度封装的组件化模块而言,需要内部状态变量维持组件自身的独立运行
当应用复杂度上升之后,状态变量的数量急剧增多,并且它们之间能相互变化影响
自然的状态管理就比较困难了,容易造成状态的变化不受控制
这个时候引入状态管理工具来管理应用的众多状态数据就显得格外的重要

mobx 就是这样一个来管理状态数据的工具

mobx 与 redux 的关系

它两同样都是管理应用程序的状态

  • 开发难度低
    redux api 是函数式变量风格,对于入门开发者来说,使用 redux 没有那么容易
    mobx 使用语义化丰富的响应式编程风格,降低了学习成本,同时,集成度稍高,避免引入很多零散的第三方库

  • 开发代码量少
    redux 应用了 reducer,store 等众多概念,每增加一个状态都需要同步更新这些位置,代码较多
    而 mobx 只要在 store 中更新即可,在代码编写量上大大少于 redux

  • 渲染性能好
    在 react 中,合理编写 shouldComponentUpdate 可以避免不必要的重渲染,提升页面性能,但如果数据太复杂,实现这个方法并非易事
    mobx 能精确描述哪些是需要重渲染的,通过合理的组织组件层级和数据结构位置,可以轻易的将数据重渲染限定在最小的位置之内

综合来说,mobx 能提供比 redux 学习成本更低、对性能优化更友好的解决方案

对于既使用过 redux 又正在使用 mobx 的我来说,mobx 确实在代码量和第三方库以及维护成本上来说,要比 redux 更低点,但也正因为这样它的封装度更高,不利于接触底层的逻辑

mobx 的核心思想

概括来说,mobx 有几下几处核心点

  • 状态变化引起的副作用应该被自动被触发
    一方面是指应用逻辑只需要修改状态数据即可,mobx 会自动触发缓存,渲染 UI 等业务层面的副作用
    另一方面是副作用依赖哪些业务数据是被自动收集的,比如某个副作用依赖状态 A 和 B,如果状态 C 变化,副作用是不会被触发的
    这是 mobx 能够轻易优化视图渲染性能的关键所在

  • 控制流也是单向数据流模式

    借用官方图和网图来更好的理解

image

image

简单来说,就是 Action => State => Reaction,用 Action 函数来操作 State,State 的变化自动到 Reaction 副作用

基础语法

学习 mobx 需要我们提前掌握 ES6 的两个语法知识

  • class 类定义语法
    JS 是一门面向对象的编程语言,在开发大型复杂应用逻辑时,不可避免需要用类来封装和复用代码逻辑
    所以我们需要掌握 class 的 继承、多态特性

  • decorator 修饰器语法
    decorator 是在声明阶段实现类与类成员注解的一种语法

mobx 常用 API

可观察的数据(observable)

observable 是一种让数据的变化可以被观察的方法
同时,observable 上还挂载了另一个方法 box: observable.box

对于原型类型:String Number Boolean Symbol,以及对象和数组这些数据类型都是可被观察的

mobx 对任意变量的处理方式有两种

  • 对于数组、对象和 ES6 中的 map,可直接把 observable 当作函数,来把变量转换为可观察的对象
const arr = observable(['a', 'b', 'c'])
const obj = observable({a: 1, b: 2})
const map = observable(new Map())
  • 对与上面没有包含的其他变量类型,通过 observable.box 来将变量包装为可观察的数据
var num = observable.box(30)
var str = observable.box('hello')
var bool = observable.box(true)

// get方法用于返回原始类型值 set方法修改原始类型值
num.set(50)
console.log(num.get())

store 中的写法 - decorator

class Store {
  @observable array = []
  @observable obj = {}
  @observable map = new Map()

  @observable string = 'hello'
  @observable number = 20
  @observable bool = false

  // computed 作为 decorator 来修饰类的 get 成员
  @computed get mixed() {
    return store.string + '/' + store.number
  }

  @action bar() {
    this.string = 'world'
    this.number = 30
  }
  // @action.bound bar() {
  //   this.string = 'world'
  //   this.number = 30
  // }
}

此时会识别 observable 是普通函数还是 decorator,如果是 decorator,则会自动判断是普通数据类型还是复杂类型,自动调用box方法

对可观察的数据做出反应

  • computed
    对可观察数据做出的反应,有两种用法:普通函数和 decorator
    函数角度:

    var store = new Store()
    // computed 接收无参数的函数,内部可引用其他可观察数据,返回一个计算值
    var foo = computed(function() { return store.string + '/' + store.number })
    // 监视数据变化 使用计算值的 observe 方法
    foo.observe(function(change) {
        console.log(change) // change包含了foo的新值newValue和旧值oldValue
    })
    store.string = 'world'
    store.number = 30

    decorator:通过 store.mixed 只能获取最终的计算值。如 Store 类中的 mixed 方法

  • autorun
    autorun 会自动运行传入 autorun 的函数参数,同时修改 autorun 中引入的参数会触发自动运行

    // autorun 接收无参数的函数
    autorun(() => {
        // console.log(store.string + '/' + store.number)
        console.log(store.mixed)
    })
    store.string = 'world'
    store.number = 30
    // 会触发多次运行打印
  • when
    接收两个函数参数。第一个函数参数根据观察对象返回一个布尔值,当布尔值为 true 的时候再执行第二个函数参数

    when(() => store.bool, () => console.log('xxx'))
    store.bool = true

    如果第一个函数参数初始化就返回 true,则初始化是会直接执行第二个函数参数

    但如果初始化的时候只想记录有哪些可观察对象,而不用触发后面的副作用函数。这个时候就可以用到reaction API

  • Reaction
    接收两个函数参数。第一个函数引用可观察数据并返回一个值,这个值会作为第二个函数的参数

    reaction(() => [store.string, store.number], arr => console.log(arr.join(',')))
    store.string = 'world'
    store.number = 30

    第一个函数初始化时会执行,这样 mobx 就会知道哪些可观察对象被引用了
    并在数据被修改后,执行后面的第二个函数。这样不必执行副作用,就可以建立起副作用到可观察数据的联系

总结来说

  • computed 能将多个可观察数据组合成一个可观察数据 ( store.mixed )
  • autorun 能自动追踪其所引用的可观察数据,并在数据发生变化时重新出发
  • when 提供了条件执行逻辑,算是 autorun 的一种变种
  • Reaction 通过分离可观察数据声明,以副作用的方式对 autorun 进行了改进

修改可观察数据( action )

频繁的数据变动会触发副作用,性能会有一定损失。为了提升频繁触发副作用造成的性能问题,mobx 引入了 action 的概念
action 被定义为任何修改状态的行为,核心是将多次对状态数据的赋值合并成一次

与 observable、computed 类似,action 也是可作为普通函数和 decorator 使用,最常用也是推荐的方式是 decorator

store.bar() // 只会执行一次

action.bound 与action类似,会将被修饰的方法的上下文强制绑定到该对象上

var bar = store.bar
bar() // 放在当前的上下文执行

语法糖:runInAction,随时定义匿名的 action 方法可以运行它

runInAction(() => {
  store.string = 'world'
  store.number = 30
})

对于有多处重复调用的处理逻辑,可用 action 来实现复用,否则,用 runInAction 即可
runInAction 也可接收多一个字符串类型的参数,有利于调试

runInAction('modify', () => {
  store.string = 'world'
  store.number = 30
})

mobx应用

使用mobx-react

mobx-react 的 observer 方法可以将 react 的 render 方法包装成 autorun
observer 修饰器是修饰类本身的(组件类),不是类成员

mobx 精确知道 autorun 依赖哪些数据,做到按需触发
所以是谁真正用到被观察数据,谁就重渲染,也就需要被 observer 修饰
一般建议将所有 react 组件都被 observer 修饰,没有副作用,避免组件过多产生不渲染的问题

同时,需要将react中的用户操作行为转换为action,将数据绑定到react组件上,以驱动视图

需要注意的是:

可变数据用 @observable 修饰;get 属性或者是依赖可归纳数据的属性,建议使用 computed 修饰;mobx 上有 remove 方法,可以方便实现删除操作

常用工具函数

  • observe 监听器。注意不能监测多层次数据
  • spy 监听器。可监听所有事件,但影响性能,不建议生产环境使用
  • toJS 纯对象化。observable 数据,不包含其他额外的数据
  • trace 追踪器。在副作用中被调用,可追踪哪些组件元素渲染了,进而优化组件

提升性能

尽管 mobx 在提升性能上要好与 redux,但我们在平常项目开发中仍要注意几点

  • 细粒度拆分视图组件,可用trace检查
  • 使用专用组件处理列表
  • 尽可能晚地解构可观察数据

实例

基于以上总结,我实现了个 TodoList,包含基本的增删改查功能和性能优化

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant