生态篇-React-mobx
一 前言
本章节将继续介绍 React 的另外一个状态管理工具 React-Mobx 。希望通过本章节的学习,你能收获:
- Mobx 的特性及其基本使用;
- Mobx ,React-Mobx 原理解析(源码级别);
- Mobx 和 Redux 区别。
注意:今天讲的 Mobx 为
v6
版本,Mobx-React 为v7
版本。
二 Mobx特性
同为状态管理工具,Mobx 和 Redux 本质上上有很大的区别,但是 Mobx 和 Redux 都是独立的,不依赖于 React 本身;为了把 React 和 Mobx 关联起来,在 React 应用中更好的使用 Mobx ,出现了 mobx-react , mobx-react 提供了 HOC ,可以获取状态管理 Mobx 的数据层,也能接受 mobx 数据改变带来的更新。
①观察者模式
Mobx 采用了一种'观察者模式'——Observer
,整个设计架构都是围绕 Observer 展开:
- 在 mobx 的状态层,每一个需要观察的属性都会添加一个观察者,可以称之为
ObserverValue
。 - 有了观察者,那么就需要向观察者中收集 listener ,mobx 中有一个 Reaction 模块,可以对一些行为做依赖收集,在 React 中,是通过劫持 render 函数执行行为,进行的依赖收集。
- 如何监听改变,用自定义存取器属性中的 get 和 set ,来进行的依赖收集和更新派发,当状态改变,观察者会直接精确通知每个 listener 。
②状态提升
在正常情况下,在 React 应用中使用 Mobx ,本质上 mobx 里面的状态,并不是存在 React 组件里面的,是在外部由一个个 mobx 的模块 model 构成,每一个 model 可以理解成一个对象,状态实质存在 model 中,model 状态通过 props 添加到组件中,可以用 mobx-react 中的 Provder 和 inject 便捷获取它们,虽然 mobx 中响应式处理这些状态,但是不要试图直接修改 props 来促使更新,这样违背了 React Prop 单向数据流的原则。正确的处理方法,还是通过 model 下面的 action 方法,来改变状态,React 实质上调用的是 action 方法。
③装饰器模式
为了建立观察者模式,便捷地获取状态/监听状态,mobx 很多接口都支持装饰器模式的写法,所以在 mobx 中,装饰器模式是最常用的写法,如果不知道装饰器的同学,建议先了解一下下 ts 中decorator
,由于不是本章节的内容,我这里就不介绍了。比如如下就是 mobx 中装饰器的体现:
class Root{
@observable name = 'alien' /* 建立观察者name属性 */
@action setName(name){ this.name = name } /* 改变 name 属性 */
}
目前 typescript 已经全面支持如上写法,如果在 javascript 中直接使用会报错,所以通常需要在.babelrc
中这么配置一下:
{
"plugins":[
[
"@babel/plugin-proposal-decorators",
{
"legacy": true,
"loose": true
}
],
"@babel/plugin-proposal-class-properties",
]
}
如上添加配置后,就可以在 js 中正常使用装饰器模式了。
④精确颗粒化收集
mobx 还有一个重要特点,就是对于属性的依赖收集是精确的,颗粒化的,为什么这么说呢?比如在 mobx 一个模块如下写道:
class Root {
@observable object = { //C组件使用
name:'alien', // A组件使用
mes:'let us learn React!' // B组件使用
}
@action setName(name){ this.object.name = name }
@action setMes(mes){ this.object.mes = mes }
@action setObject(object){ this.object = object }
}
- 对于 observable 处理过的属性,每一个属性都会有 ObserverValue ,比如上面的结构会产生三个 ObserverValue ,分别对应 object ,name ,mes 。
- 当上面通过 setName 改变 name 属性的时候,只有组件 A 会更新。也就是 name ObserverValue 只收集了用到 name 的依赖项 A 组件。
- 调用 setMes 同理,只有组件 B 更新。 mes ObserverValue 只收集了 B 组件的依赖。
- 当上面通过 setObject 改变 object 的时候,即使 object 里面name ,mes 的值没有变化,也会让组件 A ,组件 B ,组件 C ,全部渲染。object 的 Observer 同样收集了name的 ObserverValue 和 mes 的 ObserverValue 。
模型图如下:
⑤引用类型处理
observable 对于引用数据类型,比如 Object ,Array ,Set ,Map等,除了新建一个 observable 之外,还会做如下两点操作。
- 一
Proxy
:会把原始对象用 Proxy 代理,Proxy 会精确响应原始对象的变化,比如增加属性——给属性绑定 ObserverValue ,删除属性——给属性解绑 ObserverValue 等。 - 二
ObservableAdministration
: 对于子代属性,会创建一个ObservableAdministration
,用于管理子代属性的ObserverValue。 - 对于外层 Root ,在
constructor
使用makeObservable
,mobx 会默认给最外层的 Root 添加 ObservableAdministration 。
三 基本用法
1 Mobx基本使用
mobx常用api
把上述每一个 class 称之为一个模块,如上述 Root 就是一个模块。mobx的 api 基本用于构建每一个响应式模块。
① makeObservable
在新版本 mobx 中,想要让整个模块变成可响应式的,那么需要在 constructor 调用 makeObservable。老版本的 mobx 不需要这么做。
constructor(){ makeObservable(this) }
② observable
会给属性值加一个观察者对象,使其能变成可观察的,当属性值改变的时候,观察者会通知每一个依赖项。
@observable name = '《React进阶实践指南》'
③action
通过 action 包裹的函数,可以用来修改 mobx 中的状态。
@action setName(newName){ this.name = newName }
④computed
根据现有的状态或其它计算值衍生出的值。如下 total 是通过 price 和 count 衍生出来的新值。
@observable price = 666 // 可观察属性——价格
@observable count = 1 // 可观察属性——数量
@computed get total() {
return this.price * this.count
}
mobx-react 常用 api
mobx-react 中的 api ,用于把 mobx 中的状态,提供给组件,并把组件也变成可观察的 —— mobx 状态改变,组件触发更新。
①Provider
用于把 mobx 的各个模块,用 Context 上下文形式,保存起来,供给组件使用。
<Provider Root={Root} > { /* ... */ } </Provider>
②inject
inject 高阶组件可以把 Provider 中的 mobx 模块,混入到组件的 props 中,所以就可以在组件中消费状态,或者调用改变状态的方法。
@inject('Root')
class Index extends React.Component{}
③observer
被 observer 高阶组件包装的组件,如果组件内部引入了 mobx 可观察属性值,当值改变的时候,会追溯到当前组件,促使当前组件更新。
@observer
class Index extends React.Component{}
上面介绍了一遍 mobx 和 mobx-react 的各个部分功能,接下来针对两种使用场景进行实践。
2 实践——实现状态共享
接下来用 mobx 实现状态共享场景。首先创建 Root 模块,用于保存全局的一些数据。
import { observable ,action ,makeObservable } from 'mobx'
class Root{
constructor(){
makeObservable(this)
}
@observable info={ name:'xxx', mes:'xxx' }
// @observable number = 1
@action setInfo(info){ this.info = info }
}
export default new Root()
根本组件注入状态:
import Root from './mobx'
export default function Index(){
return <Provider Root={Root} >
<Child />
</Provider>
}
- 全局通过 mobx-react 中的 Provider 传递内容。
使用状态:
const getUserInfo = () => {
return new Promise((resolve=>{
setTimeout(()=>{resolve({ name:'alien', mes:'let us learn React!'})
},1000)
}))
}
@inject('Root')
@observer
class Child extends React.Component{
async componentDidMount(){
/* 模拟数据交互 */
const res = await getUserInfo()
this.props.Root.setInfo(res)
}
render(){
const { info } = this.props.Root
return <div className="box" >
<p> 姓名:{info.name} </p>
<p> 想对大家说:{info.mes} </p>
</div>
}
}
- inject 引入 Root,observer 做数据响应,模拟数据交互,调用 setInfo 改变 Root 中 info 内容。 info 内容改变,重新渲染视图。
3 实践——实现组件通信
接下来模拟组件通信场景:首先注册模块用于组件通信。
class Communi {
constructor(){
makeObservable(this)
}
@observable mesA = ''
@observable mesB = ''
@action setMesA(mes){ this.mesA = mes }
@action setMesB(mes){ this.mesB = mes }
}
export default new Communi()
然后建立A,B组件实现通信功能:
@inject('Communi')
@observer
class ComponentA extends React.Component{ /* 组件A */
state={ CompAsay:''}
render(){
const { CompAsay } = this.state
const { mesB } = this.props.Communi
return <div className="box" >
<p>我是组件A</p>
<div> B组件对我说:{mesB} </div>
我对B组件说: <input onChange={(e) => this.setState({ CompAsay :e.target.value })} placeholder="CompAsay" />
<button onClick={() => this.props.Communi.setMesA(CompAsay)} >确定</button>
</div>
}
}
@inject('Communi')
@observer
class ComponentB extends React.Component{ /* 组件B */
state={ compBsay:''}
render(){
const { compBsay } = this.state
const { mesA } = this.props.Communi
return <div className="box pt50" >
<p>我是组件B</p>
<div> A组件对我说:{mesA} </div>
我对A组件说:<input onChange={(e) => this.setState({ compBsay :e.target.value })} placeholder="CompAsay" />
<button onClick={() => this.props.Communi.setMesB(compBsay)} >确定</button>
</div>
}
}
效果:
四 Mobx流程分析和原理揭秘
接下来开始正式进入 Mobx 流程分析和原理揭秘环节。从本章节的第二部分,就开始介绍了 mobx 内部,可观察属性 ObserverValue 最后会被mobx 底层处理的样子。于是顺藤摸瓜,剖析 mobx 的整个流程。
可以从三个角度分析 mobx 和 mobx-react 整个流程:
- 初始化:首先就是 mobx 在初始化的时候,是如何处理 observable 可观察属性的。
- 依赖收集:第二点就是通过 mobx-react 中的 observer ,如何收集依赖项,与 observable 建立起关系的。
- 派发更新:最后就是当改变可观察属性的值的时候,如何更新对应组件的。
比如如下在 mobx 中的一个模块这么写道(这里称之为 DEMO1 ):
class Root {
constructor(){ makeObservable(this) }
@observable authorInfo = {
name:'alien',
mes:{
say:'let us learn React!',
}
}
@observable name='《React进阶实践指南》'
@action setName(newName){ this.name = newName }
}
以上面的 DEMO1 作为基础参考。
1 模块初始化
首先是模块初始化流程。可以从 makeObservable
和 observable
入手。
首先被 observable 装饰器包裹的属性到底做了些什么呢?
①绑定状态——observable
mobx/src/api/observable.ts
function createObservable(target,name,descriptor){ // 对于如上DEMO1,target——Root类,name——属性名称 authorInfo 或者 name ,descriptor——属性描述,枚举性,可读性等
if(isStringish(name)){ /* 装饰器模式下 */
target[Symbol("mobx-stored-annotations")][name] = { /* 向类的mobx-stored-annotations属性的name属性上,绑定 annotationType_ , extend_ 等方法。 */
annotationType_: 'observable', //这个标签证明是 observable,除了observable,还有 action, computed 等。
options_: null,
make_, // 这个方法在类组件 makeObservable 会被激活
extend_ // 这个方法在类组件 makeObservable 会被激活
}
}
}
- 被 observable 装饰器包装的属性,本质上就是调用createObservable 方法。
- 通过
createObservable
将类上绑定当前 observable 对应的配置项,说白了,就是给 observable 绑定的属性添加一些额外的状态,这些状态将在类实例化的时候makeObservable
中被激活。
这里有必要先记录一下 make_
和 extend_
方法,都做了些什么。
mobx/src/types/createObservableAnnotation.ts
function make_(adm,key,descriptor){ /* */
return this.extend_(adm,key,descriptor)
}
function extend_(adm,key,descriptor){
return adm.defineObservableProperty_(key,descriptor,options)
}
- 需要记住一点就是:当调用 observable 配置项的 make_ ,本质上调用
adm.defineObservableProperty_
,至于这个是什么,马上就会讲到。
②激活状态——makeObservable
上边讲到过,在新版本 mobx 中,必须在类的 constructor 中调用makeObservable(this)
才能建立响应式。一起看一下makeObservable。
function makeObservable (target){ // target 模块实例——this
const adm = new ObservableObjectAdministration(target) /* 创建一个管理者——这个管理者是最上层的管理者,管理模块下的observable属性 */
target[Symbol("mobx administration")] = adm /* 将管理者 adm 和 class 实例建立起关联 */
startBatch()
try{
let annotations = target[Symbol("mobx-stored-annotations"] /* 上面第一步说到,获取状态 */
Reflect.ownKeys(annotations) /* 得到每个状态名称 */
.forEach(key => adm.make_(key, annotations[key])) /* 对每个属性调用 */
}finally{
endBatch()
}
}
makeObservable 主要做的事有以下两点:
- 创建一个管理者
ObservableAdministration
,上面讲到过,管理者就是为了管理子代属性的 ObservableValue 。并和模块实例建立起关系。 - 然后会遍历观察者状态下的每一个属性,将每个属性通过
adm.make_
处理,值得注意的是,这个make_是管理者的,并不是属性状态的make_,这一点不要弄混淆了。
接下来一起看一下,管理者 ObservableAdministration 里面是如何管理状态的。
③观察者属性管理者——ObservableAdministration
细心的同学应该会发现,上述初始化创建的管理者,调用的是 ObservableObjectAdministration
,实际在 mobx 内部会存在多个种类的管理者,比如数组,对象数据类型。因为不同的类型,里面的方法和状态都是不同的。本文是以对象的管理者作为参考。
mobx/src/types/observableobject.ts
class ObservableObjectAdministration{
constructor(target_,values_){
this.target_ = target_
this.values_ = new Map() //存放每一个属性的ObserverValue。
}
/* 调用 ObserverValue的 get —— 收集依赖 */
getObservablePropValue_(key){
return this.values_.get(key)!.get()
}
/* 调用 ObserverValue的 setNewValue_ */
setObservablePropValue_(key,newValue){
const observable = this.values_.get(key)
observable.setNewValue_(newValue) /* 设置新值 */
}
make_(key,annotation){ // annotation 为每个observable对应的配置项的内容,{ make_,extends }
const outcome = annotation.make_(this, key, descriptor, source)
}
/* 这个函数很重要,用于劫持对象上的get,set */
defineObservableProperty_(key,value){
try{
startBatch()
const descriptor = {
get(){ // 当我们引用对象下的属性,实际上触发的是 getObservablePropValue_
this.getObservablePropValue_(key)
},
set(value){ // 当我们改变对象下的属性,实际上触发的是 setObservablePropValue_
this.setObservablePropValue_(key,value)
}
}
Object.defineProperty(this.target_, key , descriptor)
const observable = new ObservableValue(value) // 创建一个 ObservableValue
this.values_.set(key, observable) // 设置observable到value中
}finally{
endBatch()
}
}
}
回到主流程上来,当 mobx 底层遍历观察者属性,然后调用 make_ 方法的时候,本质上调用的是如上 make_ 方法,会激活当前的 observable 属性,触发 observable 配置项上的 make_ 方法,然后就会进入真正的添加观察者属性环节 defineObservableProperty_
。
- 首先会通过 Object.defineProperty ,拦截对象的属性,添加get,set ,比如组件中引用对象上的属性,调用 get ——本质上调用
getObservablePropValue_
,在 observableValues 调用的是 get 方法;当修改对象上的属性,调用 set ——本质上调用setObservablePropValue_
,setObservablePropValue_ 调用的是 ObservableValues 上的setNewValue_
方法。 - 对于每一个属性会增加一个观察者 ObservableValue ,然后把当前 ObservableValue 放入管理者 ObservableAdministration 的 values_ 属性上。
到此为止,形成了如下的模型图结构:
2 依赖收集
如上详细介绍了初始化过程,接下来一起研究一下依赖收集流程。通过初始化过程,还遗留一点就是 ObservableValue 做了哪些事?
①观察者——ObservableValue
上面我知道了只要通过 @observable 包裹,就会创建一个 ObservableValue 。
在 Mobx 有一个核心的思想就是 Atom 主要是收集依赖,通知依赖。先来看一下 Atom 的重点方法:
mobx/src/core/atom.ts
class Atom{
observers_ = new Set() /* 存放每个组件的 */
/* value改变,通知更新 */
reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
/* 收集依赖 */
reportObserved() {
return reportObserved(this)
}
}
ObservableValue 继承了 Atom。
mobx/src/types/observablevalue.ts
class ObservableValue extends Atom{
get(){ //adm.getObservablePropValue_ 被调用
this.reportObserved() // 调用Atom中 reportObserved
return this.dehanceValue(this.value_)
}
setNewValue_(newValue) { // adm.setObservablePropValue_
const oldValue = this.value_
this.value_ = newValue
this.reportChanged() // 调用Atom中reportChanged
}
}
重点看一下在观察者属性管理者最终调用的两个方法—— get
和 setNewValue_
。
②注入模块——Provider和inject(mobx-react)
既然观察者模块已经搞定,那么接下来看一下,mobx-react
如何将模块注入到对应的组件中的。
Provider
mobx-react/src/Provider.tsx
const MobXProviderContext = React.createContext({})
export function Provider(props) {
/* ... */
return <MobXProviderContext.Provider value={value}>{children}</MobXProviderContext.Provider>
}
- mobx-react 中的 Provide r非常简单,就是创建一个上下文 context ,并通过 context.Provider 传递上下文。
inject
mobx-react/src/inject.ts
function inject(...storeNames){
const Injector = React.forwardRef(((props, ref)=>{
let newProps = { ...props }
const context = React.useContext(MobXProviderContext)
storeNames.forEach(function(storeName){ //storeNames - [ 'Root' ]
if(storeName in newProps) return
if(!(storeName in context)){
/* 将mobx状态从context中混入到props中。 */
newProps[storeName] = context[storeName]
}
})
return React.createElement(component, newProps)
}))
return Injector
}
为了让大家更清晰流程,inject 是合并加上简化后的。
- inject 作用很简单,就是将 mobx 的状态,从 context 中混入 props 中。
③可观察组件—— observer( mobx-react )
被 observe 的组件,被赋予一项功能,就是可观察的,当里面引用了 mobx 中的 ObservableValue ,当 ObservableValue 改变,组件会更新。 接下来就是核心了,需要看一下被 observe 包裹的组件会有哪些新特征,以及如何收集的依赖,又是如何更新的。被 observe 的组件分为函数组件和类组件两种情况,为了让大家明白流程,我这里只讲了类组件的情况。
observer
mobx-react/src/observer.tsx
function observer(componentClass){
/* componentClass 是类组件的情况 (函数组件我们暂且忽略) */
return function makeClassComponentObserver(){
const target = componentClass.prototype
const baseRender = target.render /* 这个是原来组件的render */
/* 劫持render函数 */
target.render = function () {
return makeComponentReactive.call(this, baseRender)
}
}
}
- 到这里基本可以弄清楚 mobx-react 中 observer HOC 的作用了——渲染 render 的劫持。通过劫持 render 函数执行,收集里面的依赖。
makeComponentReactive
mobx-react/src/observerClass.ts
function makeComponentReactive(){
const baseRender = render.bind(this) // baseRender为真正的render方法
/* 创建一个反应器,绑定类组件的更新函数 —— forceUpdate */
const reaction = new Reaction(`${initialName}.render()`,()=>{
Component.prototype.forceUpdate.call(this) /* forceUpdate 为类组件更新函数 */
})
reaction["reactComponent"] = this /* Reaction 和 组件实例建立起关联 */
reactiveRender["$mobx"] = reaction
this.render = reactiveRender
function reactiveRender() { /* 改造的响应式render方法 */
reaction.track(() => { // track中进行真正的依赖收集
try {
rendering = baseRender() /* 执行更新函数 */
}
})
return rendering
}
return reactiveRender.call(this)
}
makeComponentReactive
通过改造 render 函数,来实现依赖的收集,里面包含了很多核心流程。
- 每一个组件会创建一个 Reaction,Reaction 的第二个参数内部封装了更新组件的方法。那么如果触发可观察属性的 set ,那么最后触发更新的就是这个方法,对于类组件本质上就是的
forceUpdate
方法。 - 对 render 函数进行改造,改造成 reactiveRender ,在 reactiveRender 中,reaction.track 是真正的进行依赖的收集,track 回调函数中,执行真正的 render 方法,得到 element 对象 rendering 。
④反应器——Reaction
那么接下来重点看一下 Reaction 如何处理更新函数,还有就是 track 方法是如何收集依赖的。在如下 track 中,我标记了三个阶段,阅读的同学请细心看这个三阶段都做了些什么。
mobx/src/core/reaction.ts
class Reaction{
constructor(name_,onInvalidate_){
this.name_ = name_
this.onInvalidate_ = onInvalidate_ /* onInvalidate_ 里面有组件的forceUpdate函数,用于更新组件 */
}
onBecomeStale_(){
this.schedule_() /* 触发调度更新 */
}
/* 开启调度更新 */
schedule_(){
if (!this.isScheduled_) {
this.isScheduled_ = true
globalState.pendingReactions.push(this)
runReactions()
}
}
/* 更新 */
runReaction_(){
startBatch()
this.isScheduled_ = false
const prev = globalState.trackingContext
globalState.trackingContext = this
this.onInvalidate_() /* 更新组件 */
globalState.trackingContext = prev
endBatch()
}
/* 收集依赖 */
track(fn){
startBatch()
/* 第一阶段 */
const prevTracking = globalState.trackingDerivation
globalState.trackingDerivation = this
/* 第二阶段 */
const result = fn.call(context)
globalState.trackingDerivation = prevTracking
/* 第三阶段 */
bindDependencies(this)
}
}
这个函数特别重要,是整个收集依赖核心。
- 第一阶段: 首先在执行 track 的时候,会把全局变量的
trackingDerivation
,指向当前的 trackingDerivation 。这样在收集依赖的过程中,可以直接收集当前的 trackingDerivation ,也就是为什么 ObservableValue 能精确收集每一个 Reaction 。 - 第二阶段:首先当被 observer 包装的组件,只要执行 render 函数,就会执行 track 方法,
fn.call(context)
,真正的r ender 函数会在里面执行,如果在 render 的过程中,引用了 mobx 可观察模块,比如:
@inject('Root')
class Index extends React.Component{
render(){
return <div>
<p>{ this.props.Root.name }</p>
<button onClick={() => this.props.Root.setName('《React进阶实践指南》666')} >改变Mobx中name</button>
</div>
}
}
- 第二阶段:当如上 render 执行的时候,首先会触发 track ,将当前Reaction 赋值给 trackingDerivation ,然后访问了 Root 下面的name 属性,那么首先会触发观察状态管理者的 adm 的 getObservablePropValue_ ,接下来会触发 name 属性的观察者 ObservableValue 下面的 get 方法,最后执行的是
reportObserved(this)
,看一下 reportObserved 里面做了写什么?
mobx/src/core/observable.ts
function reportObserved(observable){
/* 此时获取到当前函数对应的 Reaction。 */
const derivation = globalState.trackingDerivation
/* 将当前的 observable 存放到 Reaction 的 newObserving_ 中。 */
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
}
- 第二阶段:reportObserved 做的事情非常直接,就是将当前的 observable 放入 Reaction 的 newObserving_ 中,这样就把观察者属性(如上例子中的name)和组件对应的 Reaction 建立起关联。
当组件中 render 函数执行完毕,也就是 jsx 中的依赖全部收集完成,就会到第三阶段,细心的同学发现,上述只是 ObservableValue 到 Reaction 收集,但是没有 Reaction 到 ObservableValue ,也就是说 ObservableValue 里面还没有组件的 Reaction,别着急,这个都是第三阶段的 bindDependencies
做的事。
mobx/src/core/derivation.ts
function bindDependencies(Reaction){ /* 当前组件的 Reaction */
const prevObserving = derivation.observing_ /* 之前的observing_ */
const observing = (derivation.observing_ = derivation.newObserving_!) /* 新的observing_ */
let l = prevObserving.length
while (l--) { /* observableValue 删除之前的 Reaction */
const observableValue = prevObserving[l]
observable.observers_.delete(Reaction)
}
let i0 = observing.length
while (i0--) { /* 给renderhanobservableValue重新添加 Reaction */
const observableValue = observing[i0]
observable.observers_.add(Reaction)
}
}
- 第三阶段: bindDependencies 主要做的事情如下:
① 对于有当前 Reaction的 observableValue,observableValue会统一删除掉里面的 Reaction。
② 会给这一次 render 中用到的新的依赖 observableValue ,统一添加当前的 Reaction 。
③ 还会有一些细节,比如说在 render 中,引入两次相同的值(如上的 demo 中的 name ),会统一收集一次依赖。
依赖收集流程图:
3 派发更新
接下来就是一次更新中,比如在( DEMO1 )中点击按钮,通过 action ,改变 mobx 中的 name 属性。那么会发生什么呢。
- 第一步: 首先对于观察者属性管理者 ObservableAdministration 会触发 setObservablePropValue_ ,然后找到对应的 ObservableValue 触发 setNewValue_ 方法。
- 第二步: setNewValue_ 本质上会触发Atom中的reportChanged ,然后调用
propagateChanged
。首先来看一下propagateChanged:
mobx/src/core/observable.ts
function propagateChanged(observable){
observable.observers_.forEach((Reaction)=>{
Reaction.onBecomeStale_()
})
}
调用 propagateChanged
触发,依赖于当前组件的所有 Reaction 会触发 onBecomeStale_ 方法。
- 第三步: Reaction 的 onBecomeStale_ 触发,会让Reaction 的 schedule_ 执行,注意一下这里 schedule_ 会开启更新调度。什么叫更新调度呢。就是 schedule_ 并不会马上执行组件更新,而是把当前的 Reaction 放入 globalState.pendingReactions(待更新 Reaction 队列)中,然后会执行 runReactions 外部方法。
mobx/src/core/reaction.ts
function runReactions(){
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
globalState.isRunningReactions = true
const allReactions = globalState.pendingReactions
/* 这里的代码是经过修改过后的,源码中要比 */
allReactions.forEach(Reaction=>{
/* 执行每一个组件的更新函数 */
Reaction.runReaction_()
})
globalState.pendingReactions = []
globalState.isRunningReactions = false
}
- 第四步: 执行每一个 Reaction ,当一个 ObservableValue 的属性值改变,可以收集了多个组件的依赖,所以 mobx 用这个调度机制,先把每一个 Reaction 放入 pendingReactions 中,然后集中处理这些 Reaction , Reaction 会触发
runReaction_()
方法,会触发 onInvalidate_ ——类组件的 forceupdate 方法完成组件更新。
借此完成整个流程。
状态派发流程图:
五 Mobx与Redux区别
- 首先在 Mobx 在上手程度上,要优于 Redux ,比如 Redux 想使用异步,需要配合中间价,流程比较复杂。
- Redux 对于数据流向更规范化,Mobx 中数据更加多样化,允许数据冗余。
- Redux 整体数据流向简单,Mobx 依赖于 Proxy, Object.defineProperty 等,劫持属性 get ,set ,数据变化多样性。
- Redux 可拓展性比较强,可以通过中间件自定义增强 dispatch 。
- 在 Redux 中,基本有一个 store ,统一管理 store 下的状态,在 mobx 中可以有多个模块,可以理解每一个模块都是一个 store ,相互之间是独立的。
六 总结
希望通过本章节的学习,可以学到一下内容:
- mobx 基本使用,实践状态管理和组件通信两种场景。
- mobx 和 mobx-react 原理。
- mobx 和 redux 的区别。