Open lulujianglab opened 4 years ago
由于近期 react 项目的数据状态是用 mobx 来管理,所以为了尽快上手项目,花了点时间研究了下,效果显著,所以特意总结了以下内容,可以用来快速理解 mobx 和上手项目
首先我们还是来快速介绍下 mobx 以及为什么需要 mobx
mobx 是一个前端领域的 js 框架,官方定义为简单、可扩展的状态管理
我们知道对于项目应用来说,数据是必不可少的,数据变量会记录某个时刻的状态,从而影响应用下一个时刻的状态 特别是对于高度封装的组件化模块而言,需要内部状态变量维持组件自身的独立运行 当应用复杂度上升之后,状态变量的数量急剧增多,并且它们之间能相互变化影响 自然的状态管理就比较困难了,容易造成状态的变化不受控制 这个时候引入状态管理工具来管理应用的众多状态数据就显得格外的重要
mobx 就是这样一个来管理状态数据的工具
它两同样都是管理应用程序的状态
开发难度低 redux api 是函数式变量风格,对于入门开发者来说,使用 redux 没有那么容易 mobx 使用语义化丰富的响应式编程风格,降低了学习成本,同时,集成度稍高,避免引入很多零散的第三方库
开发代码量少 redux 应用了 reducer,store 等众多概念,每增加一个状态都需要同步更新这些位置,代码较多 而 mobx 只要在 store 中更新即可,在代码编写量上大大少于 redux
渲染性能好 在 react 中,合理编写 shouldComponentUpdate 可以避免不必要的重渲染,提升页面性能,但如果数据太复杂,实现这个方法并非易事 mobx 能精确描述哪些是需要重渲染的,通过合理的组织组件层级和数据结构位置,可以轻易的将数据重渲染限定在最小的位置之内
综合来说,mobx 能提供比 redux 学习成本更低、对性能优化更友好的解决方案
对于既使用过 redux 又正在使用 mobx 的我来说,mobx 确实在代码量和第三方库以及维护成本上来说,要比 redux 更低点,但也正因为这样它的封装度更高,不利于接触底层的逻辑
概括来说,mobx 有几下几处核心点
状态变化引起的副作用应该被自动被触发 一方面是指应用逻辑只需要修改状态数据即可,mobx 会自动触发缓存,渲染 UI 等业务层面的副作用 另一方面是副作用依赖哪些业务数据是被自动收集的,比如某个副作用依赖状态 A 和 B,如果状态 C 变化,副作用是不会被触发的 这是 mobx 能够轻易优化视图渲染性能的关键所在
控制流也是单向数据流模式
借用官方图和网图来更好的理解
简单来说,就是 Action => State => Reaction,用 Action 函数来操作 State,State 的变化自动到 Reaction 副作用
Action => State => Reaction
学习 mobx 需要我们提前掌握 ES6 的两个语法知识
class 类定义语法 JS 是一门面向对象的编程语言,在开发大型复杂应用逻辑时,不可避免需要用类来封装和复用代码逻辑 所以我们需要掌握 class 的 继承、多态特性
decorator 修饰器语法 decorator 是在声明阶段实现类与类成员注解的一种语法
observable 是一种让数据的变化可以被观察的方法 同时,observable 上还挂载了另一个方法 box: 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 ```js 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 就会知道哪些可观察对象被引用了 并在数据被修改后,执行后面的第二个函数。这样不必执行副作用,就可以建立起副作用到可观察数据的联系
总结来说
频繁的数据变动会触发副作用,性能会有一定损失。为了提升频繁触发副作用造成的性能问题,mobx 引入了 action 的概念 action 被定义为任何修改状态的行为,核心是将多次对状态数据的赋值合并成一次
与 observable、computed 类似,action 也是可作为普通函数和 decorator 使用,最常用也是推荐的方式是 decorator
store.bar() // 只会执行一次
action.bound 与action类似,会将被修饰的方法的上下文强制绑定到该对象上
var bar = store.bar bar() // 放在当前的上下文执行
语法糖:runInAction,随时定义匿名的 action 方法可以运行它
runInAction
runInAction(() => { store.string = 'world' store.number = 30 })
对于有多处重复调用的处理逻辑,可用 action 来实现复用,否则,用 runInAction 即可 runInAction 也可接收多一个字符串类型的参数,有利于调试
runInAction('modify', () => { store.string = 'world' store.number = 30 })
mobx-react 的 observer 方法可以将 react 的 render 方法包装成 autorun observer 修饰器是修饰类本身的(组件类),不是类成员
mobx 精确知道 autorun 依赖哪些数据,做到按需触发 所以是谁真正用到被观察数据,谁就重渲染,也就需要被 observer 修饰 一般建议将所有 react 组件都被 observer 修饰,没有副作用,避免组件过多产生不渲染的问题
同时,需要将react中的用户操作行为转换为action,将数据绑定到react组件上,以驱动视图
需要注意的是:
可变数据用 @observable 修饰;get 属性或者是依赖可归纳数据的属性,建议使用 computed 修饰;mobx 上有 remove 方法,可以方便实现删除操作
尽管 mobx 在提升性能上要好与 redux,但我们在平常项目开发中仍要注意几点
基于以上总结,我实现了个 TodoList,包含基本的增删改查功能和性能优化
由于近期 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 能够轻易优化视图渲染性能的关键所在
控制流也是单向数据流模式
借用官方图和网图来更好的理解
简单来说,就是
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 当作函数,来把变量转换为可观察的对象
对与上面没有包含的其他变量类型,通过 observable.box 来将变量包装为可观察的数据
// get方法用于返回原始类型值 set方法修改原始类型值 num.set(50) console.log(num.get())
此时会识别 observable 是普通函数还是 decorator,如果是 decorator,则会自动判断是普通数据类型还是复杂类型,自动调用box方法
对可观察的数据做出反应
computed 对可观察数据做出的反应,有两种用法:普通函数和 decorator 函数角度:
decorator:通过 store.mixed 只能获取最终的计算值。如 Store 类中的 mixed 方法
autorun autorun 会自动运行传入 autorun 的函数参数,同时修改 autorun 中引入的参数会触发自动运行
when 接收两个函数参数。第一个函数参数根据观察对象返回一个布尔值,当布尔值为 true 的时候再执行第二个函数参数
如果第一个函数参数初始化就返回 true,则初始化是会直接执行第二个函数参数
但如果初始化的时候只想记录有哪些可观察对象,而不用触发后面的副作用函数。这个时候就可以用到reaction API
Reaction 接收两个函数参数。第一个函数引用可观察数据并返回一个值,这个值会作为第二个函数的参数
第一个函数初始化时会执行,这样 mobx 就会知道哪些可观察对象被引用了 并在数据被修改后,执行后面的第二个函数。这样不必执行副作用,就可以建立起副作用到可观察数据的联系
总结来说
修改可观察数据( action )
频繁的数据变动会触发副作用,性能会有一定损失。为了提升频繁触发副作用造成的性能问题,mobx 引入了 action 的概念 action 被定义为任何修改状态的行为,核心是将多次对状态数据的赋值合并成一次
与 observable、computed 类似,action 也是可作为普通函数和 decorator 使用,最常用也是推荐的方式是 decorator
action.bound 与action类似,会将被修饰的方法的上下文强制绑定到该对象上
语法糖:
runInAction
,随时定义匿名的 action 方法可以运行它对于有多处重复调用的处理逻辑,可用 action 来实现复用,否则,用 runInAction 即可 runInAction 也可接收多一个字符串类型的参数,有利于调试
mobx应用
使用mobx-react
mobx-react 的 observer 方法可以将 react 的 render 方法包装成 autorun observer 修饰器是修饰类本身的(组件类),不是类成员
mobx 精确知道 autorun 依赖哪些数据,做到按需触发 所以是谁真正用到被观察数据,谁就重渲染,也就需要被 observer 修饰 一般建议将所有 react 组件都被 observer 修饰,没有副作用,避免组件过多产生不渲染的问题
同时,需要将react中的用户操作行为转换为action,将数据绑定到react组件上,以驱动视图
需要注意的是:
常用工具函数
提升性能
尽管 mobx 在提升性能上要好与 redux,但我们在平常项目开发中仍要注意几点
实例
基于以上总结,我实现了个 TodoList,包含基本的增删改查功能和性能优化