vue3 학습 소스코드 노트 (초보 입문 시리즈) ------ 핵심 포인트! 반응형 원리 코드의 라인별 분석

주목

이 글에서는 설정, 시계 계산 등의 주요 프로세스 업데이트에 대해서만 다루며, 나중에 분석할 예정입니다.
질문이 있는 경우 소스 코드로 이동하여 답을 찾으세요.
반응형 데이터는 언제 생성되었나요?
종속성 수집은 언제 수행되나요?
반응형 데이터 업데이트 후 업데이트를 배포하는 방법은 무엇입니까?

본 글에서 사용하는 테스트 케이스는
반드시 디버깅을 해야 하는데, 그렇지 않으면 디버깅을 따라하다 보면 헷갈리기 쉽습니다.

 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`)
  })

반응형 데이터 생성

이전 글에서 초기화 설정이 어느 단계에서 실행되었는지 아직도 기억하시나요?

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

setup이 초기화되면 반응형 데이터가 생성되며,
테스트 케이스에서는 App 컴포넌트의 setup 기능이 가장 먼저 실행됩니다.

 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})])
        }
      }
    }

Ref와 Reactive의 핵심 역할

먼저 결론에 대해 이야기해 보겠습니다.
데이터 기반 뷰 업데이트를 위한 브리지입니다. 종속성 수집(getter) 및 디스패치 업데이트(setter)가 모두 포함되어 있습니다.

ref와 반응성 사이에는 큰 차이가 없습니다(프록시는 기본 데이터 유형에 대한 프록시 역할을 할 수 없으므로 vue3 자체는 클래스 클래스의 get set을 사용하여 프록시 작업을 수행합니다. 후속 종속성 수집 및 배포 업데이트 원칙은 기본적으로 동일합니다) 반응성으로) 아래에서는 반응성만 분석합니다.

  1. 먼저 프록시 객체의 유형을 결정하고 분류합니다.
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. 다양한 카테고리에 따라 다양한 getter setter 방법을 선택하고,
    가장 일반적인 객체 배열 프록시만 분석하세요.
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
}

mutableHandlers인 baseHandlers로 이동합니다.

  1. mutableHandlers에서 데이터 프록시가 작동하는 방식

컬렉션 추적 방법에 의존하는 get first의 핵심을 살펴보겠습니다.

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
  }
}

종속성 수집 추적

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)
  }
}


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
    }
  }
}

여기에 이미지 설명을 삽입하세요.

설명하다:

depsMap의 effect[]는 업데이트가 전달될 때마다 효과 배열의 반응성 효과를 실행하는 데 사용됩니다(실제로 반응성 효과 인스턴스의 실행 메소드 호출).

ReactiveEffect에서 run 메소드가 실행되면 initDepMarkers 메소드가 deps 배열의 각 객체에 추가되어 종속성 컬렉션에서 수집 및 처리되었음을 나타내는 w 속성을 추가합니다. track----> trackEffects가 추가됩니다. depsMap의 dep에(이 dep 및 effect 인스턴스는 deps의 각 객체에 해당함) n을 할당합니다(새로 수집되었음을 나타냄).

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
  }
}

effect.run()에 등록된 fn 함수가 실행되면 불필요한 업데이트 로직 트리거를 피하기 위해 이번 dep 라운드에서 수집되지 않은 효과를 삭제하기 위해 finalizeDepMarkers가 호출됩니다.

그런 다음 설명하기 위한 테스트 케이스
여기에 이미지 설명을 삽입하세요.

종속성 수집의 첫 번째 라운드는 언제 발생합니까?

App 컴포넌트에서 Side Effect 기능을 초기화할 때, ReactiveEffect가 먼저 생성되어 app.instance에 마운트됩니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

앱은 첫 번째 구성 요소를 마운트하기 위해 적극적으로 인스턴스.update()를 트리거합니다.
이전 장에서는 컴포넌트의 첫 번째 마운트 과정을 설명했는데,
실제 호출은 반응성Effect.run ----> componentUpdateFn 실행 -> 렌더링(하위 트리 생성) ----> 패치 ----> processElement 입니다.

렌더링 프로세스에 사용되는 반응형 데이터는 독립적으로 수집됩니다.
이 시점에서 app 컴포넌트의 이번 라운드의 dependency 수집이 완료됩니다. Obj와 age가 사용됩니다. 이때
여기에 이미지 설명을 삽입하세요.
app 컴포넌트 processElement가 실행됩니다. 하위 컴포넌트 child가 있으므로 mountChild가 실행됩니다. ----> patch —> 자식 컴포넌트의 processComponent 자식 컴포넌트도 앱 컴포넌트와 동일합니다.인스턴스를 초기화하고 ReactiveEffect를 생성하고 업데이트를 트리거하고 자식에 속한 componentUpdateFn을 실행한 후 자식 컴포넌트의 렌더링 기능을 실행합니다. . 하위 구성 요소는 종속성을 수집합니다.

여기에 이미지 설명을 삽입하세요.
연령 상태 추가

이번 종속성 수집 라운드가 완료되었습니다.

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

설정 단계에서 반응형 데이터가 변경되면 종속성 수집이 발생합니까?

例如:
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等钩子函数)中访问响应式数据会触发依赖收集 (后面再分析)

업데이트 배포

배포 업데이트는 언제 시작되나요?

위의 컴포넌트가 마운트된 후, 마운트된 라이프사이클 후크에 작성한 수정된 응답 데이터 작업이 setter를 트리거합니다. 반응형 setter 소스 코드를 살펴보겠습니다.


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
    ))

요약: 반응형 데이터가 업데이트되고 해당 depsMap이 비어 있지 않으면 구성 요소 업데이트가 트리거됩니다(업데이트 방법은 다음 질문에서 답변됩니다).

확장: 설정 단계에서 반응형 데이터를 수정하면 구성 요소 업데이트가 트리거되나요?
setup(){
    
    
   const age = ref(20)
   // 修改操作
   age.value = 10
   return ()=>{
    
    
      return h('div',[age.value]) 
   }
}
会触发 setter 操作 由于 depsMap 为空 所以不会发生派发更新

Vue는 디스패치 업데이트를 기반으로 구성 요소 업데이트 렌더링을 어떻게 트리거합니까?

여기에 이미지 설명을 삽입하세요.
업데이트 디스패치의 핵심은 effect.scheduler를 트리거하는 것입니다. (기존 컴포넌트 작성 방법은 activeEffect에 대한 스케줄러를 생성하는 것입니다.)

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

queueJob 분석


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)
    }
  }
}


요약: 마운트된 구성요소에 의해 트리거된 디스패치 업데이트는 마이크로태스크 실행 작업 큐에 수집됩니다. 메인 프로세스 매크로 작업이 실행된 후 마이크로태스크 작업 큐가 실행되어 실행 작업 트리거를 시작합니다( effect.run —> updateComponentFn).

구성 요소 부작용 기능이 실행될 때 여러 개의 응답 데이터 업데이트가 있을 때 구성 요소가 하나의 업데이트 렌더링만 트리거하도록 하려면 어떻게 해야 합니까?

위의 소스 코드 분석을 통해 구성 요소의 부작용 기능이 실행될 때 여러 반응형 데이터 업데이트가 하나의 구성 요소 업데이트만 트리거하는 이유에 대한 답을 이미 얻을 수 있습니다
.

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]) 
   }
}

onMounted에서 세 번 업데이트하고 TriggerEffect를 세 번 트리거하면 마이크로 태스크에 3개의 업데이트 작업이 입력됩니다. 들어오는 job.id가 동일하므로 업데이트 대기열에 하나의 업데이트 작업 구성 요소만 생성되고 업데이트만 됩니다. 한 번.

중복 구성 요소 종속성은 어떻게 제거됩니까?

각 렌더링 후에 구성 요소는 수집되지 않은 후속 효과를 정리합니다(각 반응 데이터에 해당하는 dep(set)의 반응성 효과에 해당).

예:

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 属性 去比较

추천

출처blog.csdn.net/weixin_45485922/article/details/132754955