# 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 中,装饰器模式是最常用的写法:
代码语言:javascript复制class Root {
@observable name = "Cell"
@action setName(name) {
this.name = name
}
}
# 精确颗粒化收集
mobx 有一个重要特点,对于属性的依赖收集是精确的,颗粒化的:
代码语言:javascript复制class Root {
// C 组件使用
@observable object = {
name: "Cell", // A 组件使用
msg: "Hello World", // B 组件使用
}
@action setName(name) {
this.object.name = name
}
@action setMsg(msg) {
this.object.msg = msg
}
@action setObject(object) {
this.object = object
}
}
- 对于
observable
处理过的属性,每一个属性都会有ObserverValue
,比如上面的结构会产生三个ObserverValue
,分别对应object
,name
,msg
- 当上面通过
setName
改变name
属性的时候,只有组件A
会更新,- 也就是
name ObserverValue
只收集了用到name
的依赖项A
组件
- 也就是
- 调用
setMsg
同理,只有组件B
更新msg ObserverValue
只收集了B
组件的依赖
- 当上面通过
setObject
改变object
的时候,即使object
里面name
,msg
的值没有变化,也会让组件A
,组件B
,组件C
,全部渲染object
的Observer
同样收集了name
的ObserverValue
和msg
的ObserverValue
# 引用类型处理
observable
对于引用数据类型,比如 Object
,Array
,Set
,Map
等,除了新建一个 observable
之外,还会做如下两点操作:
Proxy
:会把原始对象用Proxy
代理,Proxy
会精确响应原始对象的变化,比如增加属性——给属性绑定ObserverValue
,删除属性——给属性解绑ObserverValue
等ObservableAdministration
: 对于子代属性,会创建一个ObservableAdministration
,用于管理子代属性的ObserverValue
对于外层 Root
,在 constructor
使用 makeObservable
,mobx 会默认给最外层的 Root
添加 ObservableAdministration
。
# Mobx 基本使用
# mobx 常用 API
把上述每一个 class
称之为一个模块,如上述 Root
就是一个模块。mobx 的 api 基本用于构建每一个响应式模块。
makeObservable
- 在新版本 mobx 中,想要让整个模块变成可响应式的,那么需要在
constructor
调用makeObservable
constructor() {
makeObservable(this)
}
observable
- 会给属性值加一个观察者对象,使其能变成可观察的,当属性值改变的时候,观察者会通知每一个依赖项
@observable name = "Cell"
action
- 通过
action
包裹的函数,可以用来修改 mobx 中的状态
@action setName(name) {
this.name = name
}
computed
- 根据现有的状态或其它计算值衍生出的值
@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}>
<App />
</Provider>
inject
inject
高阶组件可以把Provider
中的 mobx 模块,混入到组件的props
中,所以就可以在组件中消费状态,或者调用改变状态的方法
@inject("Root")
class Index extends React.Component {}
observer
- 被
observer
高阶组件包装的组件,如果组件内部引入了 mobx 可观察属性值,当值改变的时候,会追溯到当前组件,促使当前组件更新
@observer
class Index extends React.Component {}
# 实践-实现状态共享
创建 Root
模块,用于保存全局的一些数据:
import { observable, action, makeObservable } from "mobx"
class Root {
constructor() {
makeObservable(this)
}
@observable info = {
name: "Cell",
age: 18,
}
@action setInfo(info) {
this.info = info
}
}
export default new Root()
根本组件注入状态:
代码语言:javascript复制import Root from "./mobx"
export default function Index() {
return (
<Provider Root={Root}>
<Child />
</Provider>
)
}
使用状态:
代码语言:javascript复制const getUserInfo = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "Cell",
age: 18,
})
}, 1000)
})
}
@inject("Root")
@observer
class Child extends React.Component {
async componentDidMount() {
const info = await getUserInfo()
this.props.Root.setInfo(info)
}
render() {
const { info } = this.props.Root
return (
<div>
<p>name: {info.name}</p>
<p>age: {info.age}</p>
</div>
)
}
}
# 实践-实现组件通信
注册模块用于组件通信:
代码语言:javascript复制class Commui {
constructor() {
makeObservable(this)
}
@observable msgA = ""
@observable msgB = ""
@action setMsgA(msg) {
this.msgA = msg
}
@action setMsgB(msg) {
this.msgB = msg
}
}
export default new Commui()
建立 A 、 B 组件实现通信功能:
代码语言:javascript复制@inject("Commui")
@observer
class CommponentA extends React.Component {
state = {
CompAsay: "",
}
render() {
const { CompAsay } = this.state
const { mesB } = this.props.Commui
return (
<div>
CommponentA
<div>from B:{mesB}</div>
<input
type="text"
value={CompAsay}
onChange={(e) => this.setState({ CompAsay: e.target.value })}
/>
<button onClick={() => this.props.Commui.setMsgA(CompAsay)}>send</button>
</div>
)
}
}
@inject("Commui")
@observer
class CommponentB extends React.Component {
state = {
CompBsay: "",
}
render() {
const { CompBsay } = this.state
const { mesA } = this.props.Commui
return (
<div>
CommponentB
<div>from A:{mesA}</div>
<input
type="text"
value={CompBsay}
onChange={(e) => this.setState({ CompBsay: e.target.value })}
/>
<button onClick={() => this.props.Commui.setMsgB(CompBsay)}>send</button>
</div>
)
}
}
# Mobx 流程分析和原理揭秘
可以从三个角度分析 mobx 和 mobx-react 整个流程:
- 初始化: mobx 在初始化的时候,是如何处理
observable
可观察属性的 - 依赖收集:通过 mobx-react 中的
observer
,如何收集依赖项,与observable
建立起关系的 - 派发更新:当改变可观察属性的值的时候,如何更新对应组件的
# 模块初始化
- 绑定状态-
observable
- 激活状态-
makeObservable
- 观察者属性管理者-
ObservableAdministration
# 依赖收集
- 观察者-
ObservableValue
- 注入模块-
Provider
和inject
- 可观察组件-
observer
- 反应器-
Reaction
# 派发更新
# Mobx 与 Redux 区别
- 在 Mobx 在上手程度上,要优于 Redux ,比如 Redux 想使用异步,需要配合中间价,流程比较复杂
- Redux 对于数据流向更规范化,Mobx 中数据更加多样化,允许数据冗余
- Redux 整体数据流向简单,Mobx 依赖于
Proxy
,Object.defineProperty
等,劫持属性get
,set
,数据变化多样性 - Redux 可拓展性比较强,可以通过中间件自定义增强
dispatch
- 在 Redux 中,基本有一个
store
,统一管理store
下的状态,在 mobx 中可以有多个模块,可以理解每一个模块都是一个store
,相互之间是独立的