Notes sur le code source d'apprentissage de vue3 (série d'introduction pour les débutants) ------ Points clés ! Analyse ligne par ligne du code de principe réactif

Remarque

Cet article ne couvrira que la mise à jour du processus principal de configuration, la montre calculée, etc. sera analysée plus tard.
Avec des questions, accédez au code source pour trouver des réponses.
Quand les données réactives ont-elles été créées ?
Quand la collecte des dépendances est-elle effectuée ?
Comment distribuer les mises à jour après une mise à jour des données réactives ?

Les cas de test utilisés dans cet article
doivent être débogués, sinon il sera facile de se tromper si vous suivez le débogage.

 it('should support runtime template compilation', async () => {
    
    
    const container = document.createElement('div')
    container.classList.add('app')
    const child = defineComponent({
    
    
      template: `
         <div><p>{
     
     {age}}---{
     
     {status?add:'hihi'}}</p></div>
      `,
      props:{
    
    
        age:{
    
    
          type: Number,
          default:20
        }
      },
      data(){
    
    
        return {
    
    
          add: '12',
          status: true
        }
      },
      mounted() {
    
    
          this.status = false
          this.add = '24'
      },
  })

    const App = {
    
    
      components:{
    
    child},
      beforeMount() {
    
    
        console.log('beforeMount');
        
      },
      data() {
    
    
        return {
    
    
        }
      },
      setup() {
    
    
        const count = ref(1)

        const age = ref('20')

        const obj  = reactive({
    
    name:'ws',address:'usa'})

        onMounted(()=>{
    
    
          obj.name = 'kd'
          count.value = 5
          age.value = '2'
        })

 
        return ()=>{
    
    
          return  h('div',[obj.name,h(child,{
    
    age:age.value})])
        }
      }
    }

  
    createApp(App).mount(container)
    await nextTick()
   
     expect(container.innerHTML).toBe(`0`)
  })

Création de données réactive

Vous souvenez-vous encore à quelle étape la configuration d'initialisation est exécutée dans l'article précédent ?

patch
processComponent
mountComponent
 // packages/runtime-dom/src/renderer.ts
  patch 阶段 组件首次挂载时
// mountComponent 方法
 
1. 先创建 组件 instance 实例
2. 初始化 setup props 等属性
3. 设置并运行带副作用的渲染函数

Lorsque la configuration est initialisée, des données réactives seront créées.
La fonction de configuration dans le composant App sera exécutée en premier dans le scénario de test.

 setup() {
    
    
        // 会创建一个 ref 响应式数据
        const count = ref(1)
        // 会创建一个 ref 响应式数据
        const age = ref('20')
        // 会创建一个 reactive 响应式数据
        const obj  = reactive({
    
    name:'ws',address:'usa'})

        onMounted(()=>{
    
    
          obj.name = 'kd'
          count.value = 5
          age.value = '2'
        })

 
        return ()=>{
    
    
          return  h('div',[obj.name,h(child,{
    
    age:age.value})])
        }
      }
    }

Le rôle central de l'arbitre et du réactif

Parlons d’abord de la conclusion :
c’est le pont pour les mises à jour des vues basées sur les données. La collection de dépendances (getter) et les mises à jour de répartition (setter) y sont toutes deux

Il n'y a pas beaucoup de différence entre ref et réactif (le proxy ne peut pas agir comme un proxy pour les types de données de base, donc vue3 lui-même utilise le get set dans la classe class pour effectuer le travail de proxy. Les principes ultérieurs de collecte et de mise à jour des dépendances sont fondamentalement les mêmes. comme réactifs). Seuls les réactifs seront analysés ci-dessous.

  1. Déterminez d’abord le type d’objet proxy et classez-le
function targetTypeMap(rawType) {
    
    
  switch (rawType) {
    
    
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}
  1. Choisissez différentes méthodes de définition de getter en fonction des différentes catégories.
    Analysez uniquement les proxys de tableau d'objets les plus courants.
export function reactive(target: object) {
    
    
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    
    
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
。。。。

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
    
    
  // 省略。。。
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

ira à baseHandlers, qui est mutableHandlers

  1. Comment fonctionnent les proxys de données dans mutableHandlers

Examinons le cœur de get first, qui repose sur la méthode de suivi de collecte.

function createGetter(isReadonly = false, shallow = false) {
    
    
  return function get(target: Target, key: string | symbol, receiver: object) {
    
    
    // 对 ReactiveFlags 的处理部分
    if (key === ReactiveFlags.IS_REACTIVE) {
    
    
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
    
    
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
    
    
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
    
    
      return target
    }

    const targetIsArray = isArray(target)

    if (!isReadonly) {
    
    
      // 数组的特殊方法处理
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
    
    
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      // 对象 hasOwnProperty 方法处理
      if (key === 'hasOwnProperty') {
    
    
        return hasOwnProperty
      }
    }

    // 取值
    const res = Reflect.get(target, key, receiver)
    // Symbol Key 不做依赖收集
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
    
    
      return res
    }
    // 进行依赖收集
    if (!isReadonly) {
    
    
      track(target, TrackOpTypes.GET, key)
    }

    // 一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露
    if (shallow) {
    
    
      return res
    }

    if (isRef(res)) {
    
    
      //跳过数组、整数 key 的展开
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
    
    
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      // 如果res 是 对象 且不是 readonly 就继续处理成 reactive
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

suivre la collection de dépendances

export function track(target: object, type: TrackOpTypes, key: unknown) {
    
    
  if (shouldTrack && activeEffect) {
    
    
    let depsMap = targetMap.get(target)
    if (!depsMap) {
    
    
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
    
    
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? {
    
     effect: activeEffect, target, type, key }
      : undefined
    // 将 activeEffect 存入到 dep 同时将 dep[] 存入到 activeEffect 中 deps 属性 上 
    trackEffects(dep, eventInfo)
  }
}



export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
    
    
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    
    
  // 如果本轮副作用函数执行过程中已经访问并收集过,则不用再收集该依赖
    if (!newTracked(dep)) {
    
    
      dep.n |= trackOpBit // set newly tracked 标识本轮已经被收集过
      shouldTrack = !wasTracked(dep)
    }
  } else {
    
    
    // Full cleanup mode. 判断现在有没有activeEffect  有activeEffect才发生依赖收集
    // activeEffect 每个组件初始化的时候会有一个activeEffect 
    // 这一步的作用 是为了避免多余的依赖收集 例如在setup 创建了 响应式数据 同步 访问 或者 修改 这个数据 这时候 都不会发生 依赖收集。只会在 执行render函数的时候 才发生依赖收集 
    shouldTrack = !dep.has(activeEffect!)
  }
  
  if (shouldTrack) {
    
    
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
  }
}


Code de base de ReactiveEffect

// 用于记录位于响应上下文中的effect嵌套层次数
let effectTrackDepth = 0
// 二进制位,每一位用于标识当前effect嵌套层级的依赖收集的启用状态
export left trackOpBit = 1
// 表示最大标记的位数
const maxMarkerBits = 30

// 当前活跃的 effect
let activeEffect;

export class ReactiveEffect {
    
    
  // 用于标识副作用函数是否位于响应式上下文中被执行
  active = true
  // 副作用函数持有它所在的所有依赖集合的引用,用于从这些依赖集合删除自身
  deps = []
  // 指针为,用于嵌套 effect 执行后动态切换 activeEffect
  parent = undefined
  // ...
  run() {
    
    
    // 若当前 ReactiveEffect 对象脱离响应式上下文
    // 那么其对应的副作用函数被执行时不会再收集依赖
    if (!this.active) {
    
    
      return this.fn()
    }
    
    // 缓存是否需要收集依赖
    let lastShouldTrack = shouldTrack
    
    try {
    
    
      // 保存上一个 activeEffect 到当前的 parent 上
      this.parent = activeEffect
      // activeEffect 指向当前的 effect
      activeEffect = this
      // shouldTrack 置成 true
      shouldTrack = true
      // 左移操作符 << 将第一个操作数向左移动指定位数
      // 左边超出的位数将会被清除,右边将会补零。
      // trackOpBit 是基于 1 左移 effectTrackDepth 位
      trackOpBit = 1 << ++effectTrackDepth
      
      // 如果未超过最大嵌套层数,则执行 initDepMarkers
      if (effectTrackDepth <= maxMarkerBits) {
    
    
        initDepMarkers(this)
      } else {
    
    
        cleanupEffect(this)
      }
      // 这里执行了 fn
      return this.fn()
    } finally {
    
    
      if (effectTrackDepth <= maxMarkerBits) {
    
    
        // 用于对曾经跟踪过,但本次副作用函数执行时没有跟踪的依赖采取删除操作。
        // 新跟踪的 和 本轮跟踪过的都会被保留
        finalizeDepMarkers(this)
      }
      
      // << --effectTrackDepth 右移动 effectTrackDepth 位
      trackOpBit = 1 << --effectTrackDepth
      
      // 返回上个 activeEffect
      activeEffect = this.parent
      // 返回上个 shouldTrack
      shouldTrack = lastShouldTrack
      // 情况本次的 parent 指向
      this.parent = undefined
    }
  }
}

Insérer la description de l'image ici

illustrer:

Le effect[] dans depsMap est utilisé pour exécuter le reactiveEffect dans le tableau d'effets à chaque fois qu'une mise à jour est distribuée (appelant en fait la méthode run de l'instance reactiveEffect)

Dans reactiveEffect, lorsque la méthode run est exécutée, la méthode initDepMarkers est ajoutée à chaque objet du tableau deps pour ajouter l'attribut w pour indiquer qu'il a été collecté et traité dans la piste de collection de dépendances ----> trackEffects sera ajouté au dépôt dans depsMap (cette instance de dépôt et d'effet correspondant à chaque objet dans deps) attribuez n (indiquant qu'il est nouvellement collecté)

export const finalizeDepMarkers = (effect: ReactiveEffect) => {
    
    
  const {
    
     deps } = effect
  if (deps.length) {
    
    
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
    
    
      const dep = deps[i]
      if (wasTracked(dep) && !newTracked(dep)) {
    
    
      // dep 类型是 set<ReativeEffect>
        dep.delete(effect)
      } else {
    
    
        deps[ptr++] = dep
      }
      // clear bits
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    deps.length = ptr
  }
}

Lorsque la fonction fn enregistrée dans effect.run() est exécutée, finalizeDepMarkers sera appelé pour supprimer les effets qui n'ont pas été collectés dans ce cycle de développement afin d'éviter le déclenchement inutile de la logique de mise à jour.

Puis des cas de tests pour illustrer
Insérer la description de l'image ici

Quand a lieu le premier cycle de collecte des dépendances ?

Lors de l'initialisation de la fonction d'effet secondaire dans le composant App, reactiveEffect sera créé en premier et monté sur app.instance
Insérer la description de l'image ici
Insérer la description de l'image ici
Insérer la description de l'image ici

L'application déclenchera activement instance.update() pour monter le premier composant.
Le chapitre précédent a expliqué le premier processus de montage du composant.
L'appel réel est reactiveEffect.run ----> exécuter componentUpdateFn —> render (générer un sous-arbre) ----> patch ----> processElement

Les données réactives utilisées dans le processus de rendu sont collectées de manière dépendante.
À ce stade, cette série de collecte de dépendances du composant d'application est terminée. Obj et age sont utilisés. À
Insérer la description de l'image ici
ce stade, le composant d'application processElement est exécuté. Puisqu'il existe un sous-composant enfant, mountChild est exécuté ----> patch —> le processComponent du composant enfant. Le composant enfant sera également le même que le composant app. Initialisez l'instance, créez ReactiveEffect, déclenchez la mise à jour, exécutez ComponentUpdateFn appartenant à l'enfant, puis exécutez la fonction de rendu du composant enfant. Le composant enfant collectera les dépendances.

Insérer la description de l'image ici
statut d'âge ajouter

Cette série de collecte de dépendances est terminée.

总结:
组件的首次依赖收集 发生在 render阶段 顺序是 父组件 setup---->父组件 render ---->子组件 setup 
----> 子组件render

La collecte des dépendances aura-t-elle lieu si les données réactives sont modifiées pendant la phase de configuration ?

例如:
setup(){
    
    
   const age = ref(20)
   // 这里发生了访问操作
   const temp = age.value
   return ()=>{
    
    
      return h('div',[age.value]) 
   }
}

这时候会触发响应式数据的 get 操作 
但是由于 没有 activeEffect(这时候 组件还没开始设置副作用函数(SetupRenderEffectFn)所以没有activeEffect) 所以不会发生依赖收集 

扩展:
setup(){
    
    
   const age = ref(20)
   setTimeout(()=>{
    
    
   // 这里发生了访问操作
      console.log(age.value);  
   })
   return ()=>{
    
    
      return h('div',[age.value]) 
   }
}
这时候 也会触发响应式数据的 get 操作 ,也是没有activeEffect(组件已经完成 effect.run 方法了,这时候 activeEffect 已经被置为空) 所以也不会发生依赖收集

后续:
在setup函数之后的生命周期(如mounted、updated等钩子函数)中访问响应式数据会触发依赖收集 (后面再分析)

Distribuer les mises à jour

Quand la mise à jour de la distribution est-elle déclenchée ?

Une fois le composant ci-dessus monté, l'opération de données réactives modifiées que j'ai écrite dans le hook de cycle de vie mouted déclenchera le setter. Jetons un coup d'œil au code source du setter réactif.


function createSetter(shallow = false) {
    
    
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    
    
    // 。。。 省略部分逻辑
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // 如果target是原型链上的东西,不要触发
    if (target === toRaw(receiver)) {
    
    
      if (!hadKey) {
    
    
        // 新增操作
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
    
    
        // 更新操作
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}


export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
    
    
  // 根据 target 查到对应的 depsMap
  const depsMap = targetMap.get(target)
  // 不存在depsMap 不触发更新
  if (!depsMap) {
    
    
    // never been tracked
    return
  }
  
  // 用于 暂存 effect
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    
    
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    
    
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
    
    
      if (key === 'length' || key >= newLength) {
    
    
        deps.push(dep)
      }
    })
  } else {
    
    
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
    
    
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
    
    
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
    
    
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
    
    
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
    
    
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
    
    
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
    
    
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
    
    
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? {
    
     target, type, key, newValue, oldValue, oldTarget }
    : undefined
 
  // 最终处理 在这里
  if (deps.length === 1) {
    
    
    if (deps[0]) {
    
    
      if (__DEV__) {
    
    
        triggerEffects(deps[0], eventInfo)
      } else {
    
    
        triggerEffects(deps[0])
      }
    }
  } else {
    
    
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
    
    
      if (dep) {
    
    
        effects.push(...dep)
      }
    }
    // 下面操作 是为了 去重 保证相同的effect 只会有一个
    if (__DEV__) {
    
    
      triggerEffects(createDep(effects), eventInfo)
    } else {
    
    
      triggerEffects(createDep(effects))
    }
  }
}


export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
    
    
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    
    
    if (effect.computed) {
    
    
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    
    
    if (!effect.computed) {
    
    
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
    
    
  if (effect !== activeEffect || effect.allowRecurse) {
    
    
    if (__DEV__ && effect.onTrigger) {
    
    
      effect.onTrigger(extend({
    
     effect }, debuggerEventExtraInfo))
    }
    // 最终会执行 scheduler 是在 初始化的时候 创建的
    if (effect.scheduler) {
    
    
      effect.scheduler()
    } else {
    
    
      effect.run()
    }
  }
}

 //  在SetupRenderEffectFn 阶段中 create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update),// 这个就是 scheduler
      instance.scope // track it in component's effect scope
    ))

Résumé : lorsque les données réactives sont mises à jour et que le depsMap correspondant n'est pas vide, la mise à jour du composant sera déclenchée (la manière de mettre à jour sera répondue dans la question suivante)

Extension : La modification des données réactives lors de la phase de configuration déclenchera-t-elle des mises à jour des composants ?
setup(){
    
    
   const age = ref(20)
   // 修改操作
   age.value = 10
   return ()=>{
    
    
      return h('div',[age.value]) 
   }
}
会触发 setter 操作 由于 depsMap 为空 所以不会发生派发更新

Comment vue déclenche-t-il le rendu des mises à jour des composants en fonction des mises à jour de répartition ?

Insérer la description de l'image ici
Le cœur de la distribution des mises à jour est de déclencher effect.scheduler (la méthode conventionnelle d'écriture de composants consiste à créer un planificateur pour activeEffect)

const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update), // effect.schedule
      instance.scope // track it in component's effect scope
    ))

Analyser la file d'attenteJob


export function queueJob(job: SchedulerJob) {
    
    
  // the dedupe search uses the startIndex argument of Array.includes() 确保不会重复设置 schedule
  // by default the search index includes the current job that is being run 默认包括正在运行的 schedule
  // so it cannot recursively trigger itself again. 避免递归触发自身再次运行
  // if the job is a watch() callback, the search will start with a +1 index to 运行在watch 中 重复运行
  // allow it recursively trigger itself - it is the user's responsibility to
  // 确保它不会陷入无限循环

  // 去重判断
  if (
    !queue.length ||
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
    )
  ) {
    
    
  //添加到队列尾部
    if (job.id == null) {
    
    
      queue.push(job)
    } else {
    
    
      // 按照 job id 自增的顺序添加 (一般父组件的id 要小于子组件 保证 父组件永远先于子组件触发更新)
      // 这个id 是由 instance.uid 决定 就是在初始化组件实例 确定( 具体代码 runtime-core/src/component) 先初始化的 uid(每次创建组件实例 全局 uid会加1) 会小,
      queue.splice(findInsertionIndex(job.id), 0, job)
    }
    queueFlush()
  }
}

// 通过promise.then 创建 微任务(去执行flushjob)
function queueFlush() {
    
    
  if (!isFlushing && !isFlushPending) {
    
    
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}


function flushJobs(seen?: CountMap) {
    
    
  // 是否正在等待执行
  isFlushPending = false
  // 正在执行
  isFlushing = true


   // 在更新前,重新排序好更新队列 queue 的顺序
  // 这确保了:
  // 1. 组件都是从父组件向子组件进行更新的。(因为父组件都在子组件之前创建的
  // 所以子组件的渲染的 effect 的优先级比较低)
  // 2. 如果父组件在更新前卸载了组件,这次更新将会被跳过。
  queue.sort(comparator)

 

  try {
    
    
  // 遍历主任务队列,批量执行更新任务
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
    
    
      const job = queue[flushIndex]
      if (job && job.active !== false) {
    
    
        if (__DEV__ && check(job)) {
    
    
          continue
        }
        // 这个 job 就是 effect.run
        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
      }
    }
  } finally {
    
    
   // 队列任务执行完,重置队列索引
    flushIndex = 0
     // 清空队列
    queue.length = 0
    // 执行后置队列任务
    flushPostFlushCbs(seen)
    // 重置队列执行状态
    isFlushing = false
    // 重置当前微任务为 Null
    currentFlushPromise = null
    // 如果主任务队列、后置任务队列还有没被清空,就继续递归执行
    if (queue.length || pendingPostFlushCbs.length) {
    
    
      flushJobs(seen)
    }
  }
}


Résumé : Les mises à jour de répartition déclenchées par le composant monté seront collectées dans une file d'attente de tâches d'exécution de microtâches. Une fois la tâche de macro du processus principal exécutée, la file d'attente de tâches de microtâches sera exécutée pour commencer à déclencher le travail d'exécution (effect.run -> updateComponentFn)

Comment garantir que le composant ne déclenchera qu'un seul rendu de mise à jour lorsqu'il y aura plusieurs mises à jour de données réactives lorsque la fonction d'effet secondaire du composant est exécutée ?

Avec l'analyse du code source ci-dessus, nous pouvons déjà comprendre pourquoi plusieurs mises à jour de données réactives ne déclenchent qu'une seule mise à jour de composant lorsque la fonction d'effet secondaire d'un composant est exécutée
.

setup(){
    
    
   const num1 = ref(20)
   
   const num2 = ref(10)

   onMounted(()=>{
    
    
     num1.value = 40
     num1.value = 50
     num2.value = 100
   })
   

   return ()=>{
    
    
      return h('div',[num1.value+num2.value]) 
   }
}

Après avoir mis à jour trois fois dans onMounted et déclenché triggerEffect trois fois, trois opérations de mise à jour seront placées dans la microtâche. Puisque le job.id entrant est le même, un seul composant de tâche de mise à jour sera créé dans la file d'attente de mise à jour et ne sera mis à jour que une fois.

Comment les dépendances de composants redondants sont-elles supprimées ?

Après chaque rendu, le composant nettoiera les effets ultérieurs qui n'ont pas été collectés (correspondant au reactiveEffect dans le dep(set) correspondant à chaque donnée responsive)

exemple:

const child = defineComponent({
    
    
      template: `
         <div><p>{
     
     {age}}---{
     
     {status?add:'hihi'}}</p></div>
      `,
      props:{
    
    
        age:{
    
    
          type: Number,
          default:20
        }
      },
      data(){
    
    
        return {
    
    
          add: '12',
          status: true
        }
      },
      mounted() {
    
    
          this.status = false
          this.add = '24'
      },
  })
// mounted 阶段 改变了 status 触发了 组件更新 重新 render 的 时候 会发生新的一轮依赖收集 
// 之前 组件 是有两个 dep 一个 属于 status 一个属于 add 但是,由于新的依赖收集 add 不会被用到 所以 在 effect.run 执行完 后 add 的 dep 会被清除掉 是根据 dep 赋值的 w 和 n 属性 去比较

Je suppose que tu aimes

Origine blog.csdn.net/weixin_45485922/article/details/132754955
conseillé
Classement