Hi!我是 Jet ,我准备在最近更新系列关于状态管理的源码解读(包含 redux、mobx、jotai),从平时开发中常用的 api 延展,欢迎关注
MobX 的主要思想是用「函数响应式编程」和「可变状态模型」使得状态管理变得简单和可扩展。
实现上是 mutable 的思想 + proxy(为了兼容性,proxy 实际上使用 Object.defineProperty 实现)。
本系列解析所使用的 mobx 版本是 6.12 , mobx-react-lite 版本是 3.1
首先是一个 todolist 的代码案例
import React from "react";
import ReactDOM from "react-dom";
import { makeAutoObservable, toJS } from "mobx";
import { observer } from "mobx-react-lite";
import "./App.css";
// 创建store
class TodoStore {
todos: Todo[] = [];
listLength: number = 0;
constructor() {
makeAutoObservable(this);
}
addTodo = (text: string) => {
this.todos.push({
id: Date.now(),
text,
completed: false,
});
this.listLength = this.todos.length;
};
toggleTodo = (id: number) => {
this.todos = this.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
this.listLength = this.todos.length;
};
}
const todoStore = new TodoStore();
console.log(toJS(todoStore));
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoProps {
todo: Todo;
toggleTodo: () => void;
}
const Todo: React.FC<TodoProps> = ({ todo, toggleTodo }) => {
return (
<li className={todo.completed ? "completed" : ""} onClick={toggleTodo}>
{todo.text}
</li>
);
};
const TodoList = observer(() => {
const [text, setText] = React.useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (text.trim() !== "") {
todoStore.addTodo(text);
setText("");
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add Todo</button>
</form>
<p>List Length: {todoStore.listLength}</p>
<ul>
{todoStore.todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
toggleTodo={() => todoStore.toggleTodo(todo.id)}
/>
))}
</ul>
</div>
);
});
ReactDOM.render(<TodoList />, document.getElementById("root"));
实现效果图:
案例拥有增加和修改功能,可以看到,我们只使用了两个 api 就完成了这个案例,非常简便,一个是 mobx
的 makeAutoObservable
,另一个是 mobx-react-lite
的 observer
,那么我们先从 makeAutoObservable
开始讲起
makeAutoObservable
makeAutoObservable
是 MobX 6 版本中新引入的 API,将普通 JavaScript 对象或类转换为可观察对象的函数。它们可以自动地将类的属性声明为可观察状态,并根据需要生成相应的 getter 和 setter 方法。我们直接看 makeAutoObservable
的源码实现
export function makeAutoObservable(target, overrides, options){
// 判断是否是普通对象(原型为 Object)
// 如果是,调用 extendObservable
// 在实例和日常开发中,一般在 class 的 constructor 使用 makeAutoObservable
// 并非直接继承于 Object,不会走这个 if
if (isPlainObject(target)) {
return extendObservable(target, target, overrides, options)
}
// initObservable 作用是开启批处理,并允许 mobx 的 global state 更改,并执行传入的函数
initObservable(() => {
// const $mobx = Symbol("mobx administration") $mobx 是一个特殊的 symbol key
// 创建了一个 ObservableObjectAdministration 对象,添加到原有的对象上,key 为上面的 $mobx
const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
// const keysSymbol = Symbol("mobx-keys") keysSymbol 也是一个特殊的 symbol key
// 获取对象的原型,本身和原型上的 key ,删除 constructor 和 $mobx 后,构建 set,存储这些 key 放到原型的 keysSymbol 上
if (!target[keysSymbol]) {
const proto = Object.getPrototypeOf(target)
const keys = new Set([...ownKeys(target), ...ownKeys(proto)])
keys.delete("constructor")
keys.delete($mobx)
addHiddenProp(proto, keysSymbol, keys)
}
// 遍历 key 对每个属性做响应式处理
target[keysSymbol].forEach(key =>
adm.make_(
key,
// must pass "undefined" for { key: undefined }
!overrides ? true : key in overrides ? overrides[key] : true
)
)
})
return target
}
打印 store 的实例可以看到,属性上有一个 mobx administration
的 ObservableObjectAdministration
对象,而原型上有一个 mobx-keys
的 set,保存了所有的 key
从名字就可以看出来 ObservableObjectAdministration
是作为管理 target 对象的 “管理员”,我们看一下 adm.make_
也就是 ObservableObjectAdministration.make_
是如何做响应式处理的
ObservableObjectAdministration.make_
make_(key: PropertyKey, annotation: Annotation | boolean): void {
if (annotation === true) {
annotation = this.defaultAnnotation_
}
if (annotation === false) {
return
}
// 校验参数,确保存在
if (!(key in this.target_)) {
if (this.target_[storedAnnotationsSymbol]?.[key]) {
return
} else {
die(1, annotation.annotationType_, `${this.name_}.${key.toString()}`)
}
}
let source = this.target_
// 在原型链上查找属性
while (source && source !== objectPrototype) {
// 就是 Object.getOwnPropertyDescriptor
const descriptor = getDescriptor(source, key)
if (descriptor) {
// 使用 annotation 注解对象处理这个 key 对应的值,将其做响应式处理
const outcome = annotation.make_(this, key, descriptor, source)
if (outcome === MakeResult.Cancel) {
return
}
if (outcome === MakeResult.Break) {
break
}
}
source = Object.getPrototypeOf(source)
}
// 记录这个值已经做了响应式处理,其实这个函数做的事情是删除原有的属性,让其 get 走响应后的值
recordAnnotationApplied(this, annotation, key)
}
可以看到先是做了一些校验和查找,然后调用了 annotation.make_(this, key, descriptor, source)
中,继续看这个函数做了什么
annotation.make_
// 1. 如果有 get 处理为 computed value
// 2. 单独绑定 value 的 set 处理为 action
// 3. 原型上的方法,生成器函数处理为 flow,其余的处理为 autoAction
// 4. 其余的变为 observable (普通)
function make_(
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor,
source: object
): MakeResult {
// 1. get 处理为 computed value
if (descriptor.get) {
return computed.make_(adm, key, descriptor, source)
}
// 2. 单独绑定 value 的 set 处理为 action
if (descriptor.set) {
const set = createAction(key.toString(), descriptor.set) as (v: any) => void
if (source === adm.target_) {
return adm.defineProperty_(key, {
configurable: globalState.safeDescriptors ? adm.isPlainObject_ : true,
set
}) === null
? MakeResult.Cancel
: MakeResult.Continue
}
defineProperty(source, key, {
configurable: true,
set
})
return MakeResult.Continue
}
// 原型上的方法,生成器函数处理为 flow,其余的处理为 autoAction
if (source !== adm.target_ && typeof descriptor.value === "function") {
if (isGenerator(descriptor.value)) {
const flowAnnotation = this.options_?.autoBind ? flow.bound : flow
return flowAnnotation.make_(adm, key, descriptor, source)
}
const actionAnnotation = this.options_?.autoBind ? autoAction.bound : autoAction
return actionAnnotation.make_(adm, key, descriptor, source)
}
let observableAnnotation = this.options_?.deep === false ? observable.ref : observable
if (typeof descriptor.value === "function" && this.options_?.autoBind) {
descriptor.value = descriptor.value.bind(adm.proxy_ ?? adm.target_)
}
// 4. 其余的变为 observable
return observableAnnotation.make_(adm, key, descriptor, source)
}
可以看到 make_
对于属性的配置做了分类,从而对不同类型的值做了不同的处理,我们定义的普通的值会被进行 observableAnnotation.make_
处理,我们追踪一下这个方法的继续调用
observableAnnotation.make_(adm, key, descriptor, source)
->
observableAnnotation.extend_(adm, key, descriptor, false)
->
ObservableObjectAdministration.defineObservableProperty_(key, descriptor.value, this.options_?.enhancer ?? deepEnhancer, false)
最终回到了 ObservableObjectAdministration
上,那我们来看一下 defineObservableProperty_
ObservableObjectAdministration.defineObservableProperty_
defineObservableProperty_(
key: PropertyKey,
value: any,
enhancer: IEnhancer<any>,
proxyTrap: boolean = false
): boolean | null {
checkIfStateModificationsAreAllowed(this.keysAtom_)
try {
startBatch()
const deleteOutcome = this.delete_(key)
if (!deleteOutcome) {
return deleteOutcome
}
if (hasInterceptors(this)) {
const change = interceptChange<IObjectWillChange>(this, {
object: this.proxy_ || this.target_,
name: key,
type: ADD,
newValue: value
})
if (!change) {
return null
}
value = (change as any).newValue
}
// get 返回为 getObservablePropValue_ ,set 返回为 setObservablePropValue_
const cachedDescriptor = getCachedObservablePropDescriptor(key)
const descriptor = {
configurable: globalState.safeDescriptors ? this.isPlainObject_ : true,
enumerable: true,
get: cachedDescriptor.get,
set: cachedDescriptor.set
}
// 这里会走 else 分支
if (proxyTrap) {
if (!Reflect.defineProperty(this.target_, key, descriptor)) {
return false
}
} else {
// 重点,将该 key 的 配置(get,set 等)用 defineProperty
// (即 Object.defineProperty)添加到对象上
defineProperty(this.target_, key, descriptor)
}
// 将该值处理为 ObservableValue
const observable = new ObservableValue(
value,
enhancer,
__DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
false
)
// this.values_ 是一个 map,保存该 key 的 值
this.values_.set(key, observable)
// 通知该 key 的 Add 事件
this.notifyPropertyAddition_(key, observable.value_)
} finally {
endBatch()
}
return true
}
看完这个函数,我们可以发现值的 get 被设置为了 ObservableObjectAdministration.getObservablePropValue_
,我们看一下这个函数
getObservablePropValue_(key: PropertyKey): any {
return this.values_.get(key)!.get()
}
可以看到我们取值时,其实是从 ObservableValue
的实例中的 values_
中取值
在 defineObservableProperty_
中,我们刚刚看到 values_
中保存的值都是 ObservableValue
的实例,也就是说,我们是从 ObservableValue.get
中取的值。
至此,我们看到了响应式的一个核心类:ObservableValue
,它表示可观察的单一值。在 MobX 中,被观察的值可以是任何类型的 JavaScript 值,比如数字、字符串、对象等等。文档对他的介绍是:
关于 ObservableValue
的源码实现,我们下一个文章再更,而且我们还剩下 mobx 对 set 方法做了哪些处理还没有说,这个留在后面再具体说,这期先写到这