vue3.0源码学习(一) —— effect 源码学习
effect 源码学习回顾、复习。
effect 源码学习
vue3.x使用示例:
const state = reactive({
name: '张三',
nickName:'张三(书记)',
age: 18,
flag:false,
n:{
n:1
}
})
effect(() => {
document.getElementById('app').innerHTML = `
<h1>${state.flag ? state.nickName : state.name}</h1>
<p>年龄: ${state.age}</p>
`;
});
一、effect 函数
1、定义ReactiveEffect类
分析:effect 接收的是一个函数,且有调度函数scheduler,当初次加载和引用的数据发生改变会重新执行effect,且effect可以在同一个文件中多次使用。所以我们需要用类来抽象出来.。
class ReactiveEffect {
public active = true //默认effect为失活状态
constructor(public fn, public scheduler){} // 接收参数
// 提供一个run方法
run(){
// 当失活状态则返回函数执行
if(!this.active) return this.fn()
return this.fn()
}
}
2、实现 effect 函数
分析:effect第一个参数为一个函数,第二个参数为一个对象,effect可以多次使用,且effect可以返回一个对象单独调用effect内部的一些方法
export function effect (fn, options){
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect .run() // 默认让响应式的effect执行一次
const runner = _effect.run.bind(_effect)// 让用户可以手动调用effect
runner.effect = _effect; // 将effect挂载到runner上,用户可以通过runner拿到effect
return runner;// 将effect返回,用户可以手动调用内部方法
}
二、依赖收集 track
1、依赖存储逻辑说明
分析: 在vue2.x和vue3的部分版本使用的方案是数组收集。在vue3高版本中使用了weakMap链路式收集依赖
//依赖收集数据结构示例:
/**
* targetMap {
* // target => object
* target:{ // new Map()
* // key => string
* key: [ // new Set
* activeEffect //effect
* ]
* }
* }
*/
为解决嵌套effect等情况加入parant和activeEffect
ReactiveEffect相关调整,以及定义全局activeEffect
export let activeEffect;
class ReactiveEffect {
constructor(public fn, private scheduler){}
public active = true// 是否激活
public deps = [] // 用于存储依赖 需要知道effect依赖了哪些属性
public parant = undefined; // 父级依赖 activeEffect
run(){
if(!this.active) return this.fn()
// 示例:
// let activeEffect = e2
// effect(()=>{ //e1 e1.parent = null e1.deps = [name, address]
// name //e1
// effect(() => { e2 //e2.parent = e1
// age //e2
// })
// address // e1
// })
// 其他情况 意味着是激活状态
try{ // 数据结构就是一个tree结构
this.parant = activeEffect; // 将当前的effect作为父级依赖
activeEffect = this; // 将当前的effect设置为activeEffect
cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
}finally{
activeEffect = this.parant; // 恢复父级依赖
this.parant = undefined; // 清空父级依赖
}
}
}
2、触发依赖收集
在响应式reactive将数据转换为响应式数据时触发依赖收集
get(target, key, receiver){
// 因为触发了get,在外部传进来的key也是ReactiveFlags.IS_REACTIVE这样就达到了标识已经被代理过的逻辑
if(key === ReactiveFlags.IS_REACTIVE) {
return true; // 如果访问的是__v_isReactive属性,返回true
}
// return target[key]
console.log('activeEffect', activeEffect, key);
track(target, key) // 依赖收集,收集当前的effect
const r = Reflect.get(target, key, receiver)
if(isObject(r)){ // 只有用户取值才会二次代理
return reactive(r)
}
// return Reflect.get(target, key, receiver) // 处理了this的指向问题
// 通过Reflect来获取属性值, receiver是代理对象
return r
},
3、track 函数实现
按照既定数据结构收集依赖
/**
* targetMap {
* // target => object
* target:{ // new Map()
* // key => string
* key: [ // new Set
* activeEffect //effect
* ]
* }
* }
*/
const targetMap = new WeakMap(); // 用WeakMap来存储依赖收集,key只能是对象
export function track(traget, key){
if(!activeEffect) {
// 说明没有在effect中执行所以不需要收集,或访问的不是响应式对象
return;
}
let depsMap = targetMap.get(target); // 获取target对应的依赖收集
if(!depsMap) {
// 如果没有依赖收集,创建一个新的 这里的depsMap内存的是具体属性
targetMap.set(target, depsMap = new Map());
}
let dep = depsMap.get(key) // 获取具体属性对应的依赖收集
if(!dep){
// 如果没有依赖收集,创建一个新的
depsMap.set(key, dep = new Set())
}
// 将当前的effect添加到依赖收集
if(!dep.has(activeEffect)){
dep.add(activeEffect) // 添加当前的effect到依赖收集
activeEffect.deps.push(activeEffect) // 将当前的依赖收集添加到activeEffect的deps中
}
// 一个属性对应多个effect,一个effect对应多个属性; 多对多
}
三、触发更新 trigger
1、当set时触发trigger更新
在响应式reactive将数据转换为响应式数据时触发依赖收集
set(target, key, value, receiver){
let oldValue = target[key] // 获取旧值
// 通过Reflect来获取属性值, receiver是代理对象
// set 方法的返回值是一个布尔值,表示是否设置成功
let r = Reflect.set(target, key, value, receiver)
if(oldValue !== value) {
trigger(target, key, value, receiver) // 触发依赖
}
return r;
}
2、track 函数实现
export function trigger(target, key, newVal, oldVal){
// weakMap { obj:key:set(effect)
const depsMap = targetMap.get(target); // 获取target对应的依赖收集
if(!depsMap){ // 没有说明没有依赖,不需要更新
return
}
const dep = depsMap.get(key); // 获取具体属性对应的依赖收集
if(dep){
let effects = [...dep]
const effects = [...dep];
effects.forEach(effect => {
// 当我重新执行此effect时,将当前的effect放到全局上 activeEffect 上
//这里的activeEffect是正在执行的effect
// 因为activeEffect重新赋值是在finally中执行的,try中执行完成后才会执行finally
// 所以当在effct中改变属性时,activeEffect还是正在执行的effect
if(activeEffect != effect) {
effect.run(); // 执行依赖收集中的effect
}
});
}
}
四、优化处理
1、cleanpEffect 避免重复收集
// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
function cleanpEffect(effect) {
// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
let { deps } = effect;
// effect.deps.length = 0; // 只清空了数组,没有清空属性中的effect
// 属性记录了effect effect {key: new Set([effect1, effect2])}
for(let i = 0; i < deps.length; i++) {
deps[i].delete(effect); // 将属性中的effect删除
}
effect.deps.length = 0; // 清空数组
}
// run 函数中加入清空逻辑
run () {
if(!this.active) {
return this.fn(); // 如果不激活,直接执行函数
}
// 其他情况 意味着是激活状态
try{ // 数据结构就是一个tree结构
this.parant = activeEffect; // 将当前的effect作为父级依赖
activeEffect = this; // 将当前的effect设置为activeEffect
cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
}finally{
activeEffect = this.parant; // 恢复父级依赖
this.parant = undefined; // 清空父级依赖
}
}
2、加入失活stop
stop(){
if(this.active){
cleanpEffect(this); // 停止时,清空当前effect的依赖
this.active = false;
}
}
五、调度函数
- trigger 中加入调度函数处理
export function trigger(target, key, newVal, oldVal){
// weakMap { obj:key:set(effect)
const depsMap = targetMap.get(target);
if(!depsMap){
return
}
const dep = depsMap.get(key);
if(dep) {
const effects = [...dep];
effects.forEach(effect => {
if(activeEffect != effect) {
if(effect.scheduler){
effect.scheduler()
}else{
effect.run(); // 执行依赖收集中的effect
}
}
});
}
}
六、完整代码
import { reactive, ReactiveFlags } from "./reactive";
import { activeEffect, track, trigger } from './effect'
import { isObject } from "@vue/shared";
export const mutableHandlers = {
get(target, key, receiver){
// 因为触发了get,在外部传进来的key也是ReactiveFlags.IS_REACTIVE这样就达到了标识已经被代理过的逻辑
if(key === ReactiveFlags.IS_REACTIVE) {
return true; // 如果访问的是__v_isReactive属性,返回true
}
// return target[key]
console.log('activeEffect', activeEffect, key);
track(target, key) // 依赖收集,收集当前的effect
const r = Reflect.get(target, key, receiver)
if(isObject(r)){ // 只有用户取值才会二次代理
return reactive(r)
}
// return Reflect.get(target, key, receiver) // 处理了this的指向问题
// 通过Reflect来获取属性值, receiver是代理对象
return r
},
set(target, key, value, receiver){
// target[key] = value
// return true
let oldValue = target[key] // 获取旧值
// 通过Reflect来获取属性值, receiver是代理对象
// set 方法的返回值是一个布尔值,表示是否设置成功
let r = Reflect.set(target, key, value, receiver)
if(oldValue !== value) {
trigger(target, key, value, receiver) // 触发依赖
}
return r;
}
}
// 1.将属性和运行的effect关联起来
// 2.当属性变化时,重新执行effect, effect存在嵌套执行的情况
// 3.停止监听
// 4.外部调用
// 5.避免重复收集
// 6.scheduler调度函数
// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
function cleanpEffect(effect) {
// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
let { deps } = effect;
// effect.deps.length = 0; // 只清空了数组,没有清空属性中的effect
// 属性记录了effect effect {key: new Set([effect1, effect2])}
for(let i = 0; i < deps.length; i++) {
deps[i].delete(effect); // 将属性中的effect删除
}
effect.deps.length = 0; // 清空数组
}
/**
* effect(()=>{ //effect1
* name;
* effect(()=>{//effect2
* age;
* })
* address
* })
*
* vue2/vue3早期实现方案为栈 [effect1, effect2]
* vue3 后续版本实现方案为打标记 parentEffectActive
* 1.在执行effect时,先将当前的EffectActive存储起来
* 2.执行完effect后,再将EffectActive恢复
* 3.在effect中执行依赖收集时,使用EffectActive作为当前的依赖
* 4.在属性变化时,使用EffectActive作为当前的依赖
*/
export let activeEffect;
class ReactiveEffect{
public active = true; // 是否激活
public deps = []; // 用于存储依赖 需要知道effect依赖了哪些属性
public parant = undefined; // 父级依赖 activeEffect
// public fn
// constructor(fn) {
// this.fn = fn
// }
// 等价于上面
constructor(public fn, private scheduler) {
// 将当前对象的父级依赖赋值给 EffectActive
}
run () {
if(!this.active) {
return this.fn(); // 如果不激活,直接执行函数
}
// 示例:
// let activeEffect = e2
// effect(()=>{ //e1 e1.parent = null e1.deps = [name, address]
// name //e1
// effect(() => { e2 //e2.parent = e1
// age //e2
// })
// address // e1
// })
// 其他情况 意味着是激活状态
try{ // 数据结构就是一个tree结构
this.parant = activeEffect; // 将当前的effect作为父级依赖
activeEffect = this; // 将当前的effect设置为activeEffect
cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
console.log('try');
return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
}finally{
console.log('finally');
activeEffect = this.parant; // 恢复父级依赖
this.parant = undefined; // 清空父级依赖
}
}
stop(){
if(this.active){
cleanpEffect(this); // 停止时,清空当前effect的依赖
this.active = false;
}
}
}
// 依赖收集就是 将当前的effect变成全局的 稍后取值的时候就可以拿到这个全局的effect
export function effect (fn, options={}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run(); // 默认让响应式的effect执行一次
const runner = _effect.run.bind(_effect); // 让用户可以手动调用effect
runner.effect = _effect; // 将effect挂载到runner上,用户可以通过runner拿到effect
return runner; // 将effect返回,用户可以手动调用stop方法
}
// 依赖收集数据结构
// 依赖收集的核心就是将当前的effect和属性关联起来
// let mapping = {
// target:{
// name:[activeEffect1, activeEffect2],
// }
// }
const targetMap = new WeakMap(); // 用WeakMap来存储依赖收集,key只能是对象
export function track(target, key){
if(!activeEffect) {
// 说明没有在effect中执行所以不需要收集,或访问的不是响应式对象
return;
}
let depsMap = targetMap.get(target); // 获取target对应的依赖收集
if(!depsMap) {
// 如果没有依赖收集,创建一个新的 这里的depsMap内存的是具体属性
targetMap.set(target, depsMap = new Map());
}
let dep = depsMap.get(key) // 获取具体属性对应的依赖收集
if(!dep) {
// 如果没有依赖收集,创建一个新的
depsMap.set(key, dep = new Set());
}
// 将当前的effect添加到依赖收集
let shouldTrack = dep.has(activeEffect)
if(!shouldTrack){
dep.add(activeEffect); // 添加当前的effect到依赖收集
activeEffect.deps.push(dep); // 将当前的依赖收集添加到activeEffect的deps中
}
// 一个属性对应多个effect,一个effect对应多个属性; 多对多
}
// 触发更新
export function trigger(target, key, newVal, oldVal){
// weakMap { obj:key:set(effect)
const depsMap = targetMap.get(target); // 获取target对应的依赖收集
if(!depsMap){
return
}
const dep = depsMap.get(key); // 获取具体属性对应的依赖收集
if(dep) {
const effects = [...dep];
effects.forEach(effect => {
// 当我重新执行此effect时,将当前的effect放到全局上 activeEffect 上
//这里的activeEffect是正在执行的effect
// 因为activeEffect重新赋值是在finally中执行的,try中执行完成后才会执行finally
// 所以当在effct中改变属性时,activeEffect还是正在执行的effect
if(activeEffect != effect) {
if(effect.scheduler){
effect.scheduler()
}else{
effect.run(); // 执行依赖收集中的effect
}
}
});
}
}