Mobx 源码分析 - array
通过以下方法将数组变成可观察数组。
@observable person = []
person = observable.array([])
person = observable([])
2
3
前言
person = observable([])
// 等同于
person = observable.array([])
2
3
这两种调用方式是一样的,只不过直接调用 observable
,会在内部判断参数类型,然后再调用 observable.array
。
@observable person = []
本质上其实还是通过 observable.array
方式调用,具体可以参考之前写的文章Mobx 源码分析 - 第一阶段汇总。
observable.array
调用 observable.array
方法其实就是实例化 ObservableArray
。
array: function (initialValues, options) {
if (arguments.length > 2)
incorrectlyUsedAsDecorator("array");
var o = asCreateObservableOptions(options);
return new ObservableArray(initialValues, getEnhancerFromOptions(o), o.name);
}
2
3
4
5
6
ObservableArray
继承原生数组,且拥有原生数组方法,并在原型上增加 $mobx
对象,对象为 ObservableArrayAdministration
实例。
如果数组有初始值,则修改全局状态 allowStateChanges
,并调用 spliceWithArray
方法,调用完成后,把之前的全局状态 allowStateChanges
值重新赋值回去。
if (initialValues && initialValues.length) {
const prev = allowStateChangesStart(true)
this.spliceWithArray(0, 0, initialValues)
allowStateChangesEnd(prev)
}
2
3
4
5
spliceWithArray
方法本质上是调用 $mobx
上方法,前面说过 $mobx
为 ObservableArrayAdministration
实例,所以也就相当于调用 ObservableArrayAdministration
的 spliceWithArray
方法。
spliceWithArray(index: number, deleteCount?: number, newItems?: T[]): T[] {
return this.$mobx.spliceWithArray(index, deleteCount, newItems)
}
2
3
为什么 console 中查看可观察的数组显示的长度为 1000
mobx
会 ObservableArray
原型上初始化 1000 个键值对,键是从 0 - 1000,值为属性描述符。
function createArrayEntryDescriptor(index: number) {
return {
enumerable: false,
configurable: false,
get: function() {
return this.get(index)
},
set: function(value) {
this.set(index, value)
}
}
}
function createArrayBufferItem(index: number) {
Object.defineProperty(ObservableArray.prototype, "" + index, createArrayEntryDescriptor(index))
}
export function reserveArrayBuffer(max: number) {
for (let index = OBSERVABLE_ARRAY_BUFFER_SIZE; index < max; index++)
createArrayBufferItem(index)
OBSERVABLE_ARRAY_BUFFER_SIZE = max
}
reserveArrayBuffer(1000)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ObservableArrayAdministration
实例化 ObservableArrayAdministration
会把 ObservableArray
的原型作为 this.array
的值,并把实例化 Atom
赋值给 this.atom
。
spliceWithArray
取有效的起始位置和删除个数。
const length = this.values.length
if (index === undefined) index = 0
else if (index > length) index = length
else if (index < 0) index = Math.max(0, length + index)
if (arguments.length === 1) deleteCount = length - index
else if (deleteCount === undefined || deleteCount === null) deleteCount = 0
else deleteCount = Math.max(0, Math.min(deleteCount, length - index))
2
3
4
5
6
7
8
如果属性上有拦截器 intercept
,则会先调用传递的拦截器函数,函数返回值会作为数组修改的依据。
const change = interceptChange<IArrayWillSplice<T>>(this as any, {
object: this.array,
type: "splice",
index,
removedCount: deleteCount,
added: newItems
})
if (!change) return EMPTY_ARRAY
deleteCount = change.removedCount
newItems = change.added
2
3
4
5
6
7
8
9
10
对于数组中每一项调用 enhancer
方法,然后更新数组的长度。如果删除个数与新增个数都不为 0,则发出更改事件,并进行衍生,最后返回 values
值。
// 更改数组长度
this.updateArrayLength(length, lengthDelta)
// 修改数组,并给将新数组赋值给 this.values,删除项赋值给 res
const res = this.spliceItemsIntoValues(index, deleteCount, newItems)
// 发送更改事件
if (deleteCount !== 0 || newItems.length !== 0) this.notifyArraySplice(index, newItems, res)
// 返回删除后每一项组成的数组
return this.dehanceValues(res)
2
3
4
5
6
7
8
get
当我们打印 person
,会打印出一个 ObservableArray
实例,但是数组的内容并不会直接显示,这是因为数组每一项都被 get
和 set
截取,每当访问其中一项,都会调用 ObservableArray
的 get
方法,并把当前项的索引 index
当做参数传递进去。
当我们使用了数组其中一项时,当前对象会通过调用 reportObserved
,向外界发出监听此对象指令,并返回当前项的值。
get(index: number): T | undefined {
const impl = <ObservableArrayAdministration<any>>this.$mobx
if (impl) {
if (index < impl.values.length) {
impl.atom.reportObserved()
return impl.dehanceValue(impl.values[index])
}
console.warn(
`[mobx.array] Attempt to read an array index (${index}) that is out of bounds (${
impl.values.length
}). Please check length first. Out of bound indices will not be tracked by MobX`
)
}
return undefined
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15