知食记
搜索文档…
computed如何与视图绑定

computed 初始化

    1.
    合并配置
    2.
    initState
    3.
    initComputed lazy: true
    4.
    遍历computed上所有的属性,并new watcher
1
for (const key in computed) {
2
if (!isSSR) {
3
watchers[key] = new Watcher(
4
vm,
5
getter || noop,
6
noop,
7
computedWatcherOptions
8
)
9
}
10
// ...
11
if (!(key in vm)) {
12
defineComputed(vm, key, userDef)
13
} else if (process.env.NODE_ENV !== 'production') {
14
// ...
15
}
16
}
17
18
// watcher.js
19
class Watcher {
20
// ...
21
constructor (
22
vm: Component,
23
expOrFn: string | Function,
24
cb: Function,
25
options?: ?Object,
26
isRenderWatcher?: boolean
27
) {
28
// ...
29
if (options) {
30
// ...
31
this.lazy = !!options.lazy
32
// ...
33
}
34
this.dirty = this.lazy // for lazy watchers
35
36
if (typeof expOrFn === 'function') {
37
this.getter = expOrFn
38
}
39
...
40
this.value = this.lazy
41
? undefined
42
: this.get()
43
}
44
}
Copied!
5. new Watcher 首次进来,lazy = dirty = true, this.value = undefined
6. 因为key不在vm上,会defineComputed, 把computed的所有属性都响应式
1
const sharedPropertyDefinition = {
2
enumerable: true,
3
configurable: true,
4
get: noop,
5
set: noop
6
}
7
export function defineComputed (
8
target: any,
9
key: string,
10
userDef: Object | Function
11
) {
12
const shouldCache = !isServerRendering() // true
13
if (typeof userDef === 'function') {
14
sharedPropertyDefinition.get = shouldCache
15
? createComputedGetter(key)
16
: createGetterInvoker(userDef)
17
sharedPropertyDefinition.set = noop
18
}
19
// ...
20
Object.defineProperty(target, key, sharedPropertyDefinition)
21
}
22
23
function createComputedGetter (key) {
24
return function computedGetter () {
25
const watcher = this._computedWatchers && this._computedWatchers[key]
26
if (watcher) {
27
// 第一次视图渲染时进入
28
if (watcher.dirty) {
29
// 第一次默认为 true
30
// 第一次会触发依赖收集, watcher.deps 是当前的所有依赖
31
watcher.evaluate()
32
}
33
// 第一次上面执行完后,当前Dep.target是当前Render!
34
if (Dep.target) {
35
// 把当前computed watcher的全部作为依赖都逐一添加到render watcher
36
// 因此逐一依赖的subs 也会有render watcher
37
// 那么所有依赖的subs 不仅有了computed watcher
38
// 也有了rener watcher
39
// 没有触发视图渲染的computed不会触发get,也不会到这里
40
// 甚至虽然computed-watch触发,但是最终返回值不变
41
// 没有导致render刷新,也不会到这里
42
watcher.depend()
43
}
44
return watcher.value
45
}
46
}
47
}
48
49
// watch.js
50
evaluate () {
51
// 脏数据时才更新
52
if (this.dirty) {
53
this.value = this.get()
54
this.dirty = false
55
}
56
// 不然读取缓存值
57
return this.value
58
}
59
60
depend () {
61
let i = this.deps.length
62
while (i--) {
63
this.deps[i].depend()
64
}
65
}
Copied!
7. 待到视图渲染时,会读取这个computed属性,由于设置了响应式,会触发上面的 watcher.evaluate
8. evaluate 中又调用了this.get
watcher.js
1
get () {
2
pushTarget(this) // 设置 Dep.target 为本watcher
3
let value
4
const vm = this.vm
5
try {
6
value = this.getter.call(vm, vm) // 触发已定义data或者props的getter依赖收集
7
} catch (e) {
8
if (this.user) {
9
handleError(e, vm, `getter for watcher "${this.expression}"`)
10
} else {
11
throw e
12
}
13
} finally {
14
// "touch" every property so they are all tracked as
15
// dependencies for deep watching
16
if (this.deep) { // 有deep属性就深度遍历
17
traverse(value)
18
}
19
popTarget() // 依赖收集完毕,出栈
20
this.cleanupDeps() // 转移watcher最新newDeps到deps上
21
}
22
return value
23
}
Copied!
这个get,会执行 this.getter.call(vm,vm)。
由于当前的props, datas 已经都响应式了,这样会触发他们的get进行依赖收集
1
Object.defineProperty(obj, key, {
2
// ...
3
get: function reactiveGetter () {
4
const value = getter ? getter.call(obj) : val
5
if (Dep.target) {
6
dep.depend()
7
// ...
8
}
9
return value
10
},
11
// ...
12
}
13
14
// dep.js
15
depend () {
16
if (Dep.target) {
17
Dep.target.addDep(this) // 目标把自己添加为依赖项
18
}
19
}
20
// watcher.js
21
// newDeps: Array<Dep>;
22
// depIds: SimpleSet;
23
// newDepIds: SimpleSet;
24
addDep (dep: Dep) {
25
const id = dep.id
26
if (!this.newDepIds.has(id)) {
27
this.newDepIds.add(id)
28
this.newDeps.push(dep)
29
if (!this.depIds.has(id)) { // 第一次并没有
30
dep.addSub(this) // 传进来的依赖,添加本watcher订阅
31
}
32
}
33
}
34
// dep.js
35
addSub (sub: Watcher) {
36
this.subs.push(sub) // 设置订阅的watcher
37
}
38
Copied!
所谓的dep.depend
就是给当前的 Dep.target 对应的 Watcher 的 newDeps数组追加实例,并且在自身的 dep.sub 数组中添加这个 watcher,以便未来更新的时候进行 dep.notify 通知
如此就把所有的computed的属性依赖收集完毕。最后返回dirty=false 和新值。下次直接读取缓存。
9. 紧着着。因为现在是render-watcher。所有Dep.target变回render,接下来的watcher.depend。会把computed所有依赖都逐一dep.depend到render-watcher中。也即,所有的computed依赖变化后,也会通知render-watcher

依赖变化

依赖变化后,如果computed-watcher的subs为0,那么仅需把dirty再置为true即可。因为没有render-watcher使用它,自然不需要刷新视图。
如果有render-watcher监听。那么在nextTick后,对所有的wacher queue进行排序。因为render-watcher id一定大于computed, 会在computed-watcher反复进行watch.run后(也仅设置dirty为true)最后的render-watch执行evaluate, 完成this.get()的读取。

参考

最近更新 1yr ago