Vue3源码III-effect补充

Vue3源码III-effect补充

停止effect更新的方法

先看使用Vue源码中停止effect更新的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { reactive, effect} from '../../../node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'

const state = reactive({name:'gzy'})
let a = 1
// 原生effect中调用effect会有一个runner返回值
const runner = effect(()=>{
console.log('runner')
app.innerHTML = state.name + a
})
runner.effect.stop(); // 停止effect的响应式能力, 不再收集相关依赖了

// 默认情况下是自动的更新, 数据变化后更新 effect
// 数据变化不更新,我可以自己决定更新
setTimeout(()=>{
state.name = 'ggggg'
// state.name = 'a'
// state.name = 'b'
a = 100
// runner.effect.run() // forceUpdate
runner()

state.name = 'zzzz'
},2000)
</script>
</body>
</html>
  • 在原生effect种调用effect会 返回一个runner函数
  • 调用runner.effect.stop() 会停止effect的响应式能力,不再收集相关依赖,不是响应式了
  • 直接调用 runner 相当于 vue2中的 forceUpdate 方法强制更新视图

根据这些特性来一步步给我们的effect中增加相关的方法

1
2
3
4
5
6
7
8
9
10
11
12
export function effect(fn){
// 创建一个响应式effect,并且让effect执行
const _effect = new ReactiveEffect(fn)
_effect.run()

// 把runner方法直接给用户, 用户可以直接去调用effect中定义的内容
const runner = _effect.run.bind(_effect)
// 可以通过runner拿到effect中的所有属性
runner.effect = _effect

return runner
}
  • runner方法能直接调用,说明调用的是_effect中的run方法
  • runner.effect.stop需要我们把 runner.effect指向_effect,并且在 class ReactiveEffect 上扩展 stop方法
  • 并且应该 有一个 active 属性来标识组件是不是正常状态
    • active = true 正常执行
    • active = false 清除相关依赖,停止依赖收集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

export class ReactiveEffect {
// 默认会将fn挂载到类的实例上
constructor(private fn){ }
parent = undefined
active = true
deps = [] // 依赖了那些列表
run(){
// 失活态默认调用run的时候 只是重新执行 不会发生依赖收集
if(!this.active){
return this.fn()
}

try{
this.parent = activeEffect
activeEffect = this

cleanupEffect(this) // 清理了上一次的依赖收集

return this.fn() // fn执行会触发依赖收集
} finally {
activeEffect = this.parent
this.parent = undefined
}
}
stop(){
if(this.active){
// 变为失活态,停止依赖收集
this.active = false
cleanupEffect(this)
}
}
}

数据变了自己来控制渲染

我们希望数据变化了但是5s后再执行页面渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { reactive, effect } from './reactivity.js'

const state = reactive({ name: 'gzy' })
let a = 1
// 原生effect中调用effect会有一个runner返回值
const runner = effect(() => {
app.innerHTML = state.name + a
}, {
scheduler: () => {
setTimeout(() => {
// 组件的异步渲染
runner()
}, 5000)
}
})

setTimeout(() => {
state.name = 'gggggg'
}, 1000)
  • effect中会传入一些参数
1
2
3
4
5
6
7
8
9
10
11
12
export function effect(fn, options: any = {}){
// 创建一个响应式effect,并且让effect执行
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run()

// 把runner方法直接给用户, 用户可以直接去调用effect中定义的内容
const runner = _effect.run.bind(_effect)
// 可以通过runner拿到effect中的所有属性
runner.effect = _effect

return runner
}
  • 在 class ReactiveEffect 中接受这个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export class ReactiveEffect {
// 默认会将fn挂载到类的实例上
constructor(private fn, public scheduler){ }
parent = undefined
active = true
deps = [] // 依赖了那些列表
run(){
// 失活态默认调用run的时候 只是重新执行 不会发生依赖收集
if(!this.active){
return this.fn()
}

try{
this.parent = activeEffect
activeEffect = this

cleanupEffect(this) // 清理了上一次的依赖收集

return this.fn() // fn执行会触发依赖收集
} finally {
activeEffect = this.parent
this.parent = undefined
}
}
stop(){
if(this.active){
// 变为失活态,停止依赖收集
this.active = false
cleanupEffect(this)
}
}
}
  • 在渲染时判断有没有传递参数
    • 传递了对应的更新函数则调用此函数
    • 没有传递则默认就是重新运行effect函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

export function trigger(target,key,newVal,oldVal){
// 通过对象找到对应的属性 让这个属性对应的effect重新执行
const depsMap = targetMap.get(target)
if(!depsMap) {
return
}

const dep = depsMap.get(key); // name 或者 age对应的所有 effect

const effects = [...dep]
// 运行的是数组 删除的是set
effects &&
effects.forEach(effect => {
// 正在执行的effect , 不要多次执行
if(effect !== activeEffect){
if(effect.scheduler){
effect.scheduler() // 用户传递了对应的更新函数则调用此函数
// 用户如果没有传递则默认就是重新运行effect函数
}else{
effect.run()
}
}
})
}

reactive深度代理

handler.ts

如果在取值的时候发现取出来的值是对象,那么再次进行代理,返回代理后的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export const mutableHandlers = {
get(target,key,receiver){
if(key === ReactiveFlags.IS_REACTIVE){
return true
}
// 如果在取值的时候发现取出来的值是对象,那么再次进行代理,返回代理后的结果
if(isObject(target[key])){
return reactive(target[key])
}

const res = Reflect.get(target,key ,receiver)
track(target,key)
return res
},
...
}