Rumah > hujung hadapan web > View.js > teks badan

Vue3 dikira dan menonton analisis kod sumber

PHPz
Lepaskan: 2023-05-11 19:49:10
ke hadapan
864 orang telah melayarinya

    dikira

    Dikira dan menonton sering ditanya tentang perbezaan mereka dalam temu bual, jadi mari kita lihat pelaksanaan khusus mereka daripada pelaksanaan kod sumber

    // packages/reactivity/src/computed.ts
    export function computed<T>(
      getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
      debugOptions?: DebuggerOptions,
      isSSR = false
    ) {
      let getter: ComputedGetter<T>
      let setter: ComputedSetter<T>
      const onlyGetter = isFunction(getterOrOptions)
      if (onlyGetter) {
        getter = getterOrOptions
        setter = __DEV__
          ? () => {
              console.warn(&#39;Write operation failed: computed value is readonly&#39;)
            }
          : NOOP
      } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
      }
      // new ComputedRefImpl
      const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
      if (__DEV__ && debugOptions && !isSSR) {
        cRef.effect.onTrack = debugOptions.onTrack
        cRef.effect.onTrigger = debugOptions.onTrigger
      }
      // 返回ComputedRefImpl实例
      return cRef as any
    }
    Salin selepas log masuk

    Anda boleh melihat bahawa pengiraan secara dalaman hanya memproses getter dan setter dahulu, dan kemudian mengembalikan ComputedRefImpl baharu Jika anda mengetahui pelaksanaan ref API, anda boleh mendapati bahawa pelaksanaannya mempunyai banyak persamaan

    ComputedRefImpl.

    // packages/reactivity/src/computed.ts
    export class ComputedRefImpl<T> {
      public dep?: Dep = undefined // 存储effect的集合
      private _value!: T
      public readonly effect: ReactiveEffect<T>
      public readonly __v_isRef = true
      public readonly [ReactiveFlags.IS_READONLY]: boolean = false
      public _dirty = true // 是否需要重新更新value
      public _cacheable: boolean
      constructor(
        getter: ComputedGetter<T>,
        private readonly _setter: ComputedSetter<T>,
        isReadonly: boolean,
        isSSR: boolean
      ) {
        // 创建effect
        this.effect = new ReactiveEffect(getter, () => {
          // 调度器执行 重新赋值_dirty为true
          if (!this._dirty) {
            this._dirty = true
            // 触发effect
            triggerRefValue(this)
          }
        })
        // 用于区分effect是否是computed
        this.effect.computed = this
        this.effect.active = this._cacheable = !isSSR
        this[ReactiveFlags.IS_READONLY] = isReadonly
      }
      get value() {
        // the computed ref may get wrapped by other proxies e.g. readonly() #3376
        // computed ref可能被其他代理包装,例如readonly() #3376
        // 通过toRaw()获取原始值
        const self = toRaw(this)
        // 收集effect
        trackRefValue(self)
        // 如果是脏的,重新执行effect.run(),并且将_dirty设置为false
        if (self._dirty || !self._cacheable) {
          self._dirty = false
          // run()方法会执行getter方法 值会被缓存到self._value
          self._value = self.effect.run()!
        }
        return self._value
      }
      set value(newValue: T) {
        this._setter(newValue)
      }
    }
    Salin selepas log masuk

    Anda dapat melihat bahawa pelaksanaan get ComputedRefImplget pada asasnya adalah sama dengan pelaksanaan ref (sila baca bab sebelumnya jika anda tidak biasa dengan pelaksanaan ref. Satu-satunya perbezaan adalah pertimbangan _dirty). nilai, yang juga sering kita panggil dikira Nilai akan dicache, jadi bagaimanakah pengiraan mengetahui bahawa nilai itu perlu dikemas kini?

    Anda boleh melihat bahawa dalam pembina yang dikira, perhubungan antara pengambil dan data responsif dalamannya akan diwujudkan Ini adalah sama seperti perhubungan antara fungsi kemas kini komponen kami dan data responsif, jadi tindak balas berkaitan kepada getter Apabila data diubah suai, penjadual yang sepadan dengan kesan getter akan dicetuskan Di sini, _dirty akan ditetapkan kepada benar dan kesan yang dikumpul akan dilaksanakan (ini biasanya kesan yang dikemas kini oleh fungsi yang dikumpul dalam get). , dan kemudian ia akan pergi Jalankan fungsi kemas kini fungsi, yang akan mencetuskan perolehan yang dikira semula Pada masa ini, kotor telah ditetapkan kepada benar, dan pengambil akan dilaksanakan semula untuk mendapatkan nilai dan pulangan baharu, dan nilai. akan dicache dalam _vlaue.

    Ringkasan:

    Jadi dikira mempunyai dua lapisan pemprosesan responsif Satu lapisan ialah hubungan antara nilai dikira dan kesan fungsi (sama dengan pelaksanaan ref hubungan antara pengiraan pengambil dan data responsif.

    Nota: Jika anda cukup berhati-hati, anda akan mendapati bahawa mungkin terdapat masalah jujukan antara kesan pencetus fungsi kemas kini fungsi dan pencetus kesan getter yang dikira. Jika terdapat data responsif a yang bukan sahaja wujud dalam getter, tetapi juga diakses lebih awal daripada getter dalam fungsi render, maka kesan fungsi kemas kini dalam dep sepadan dengan a akan dikumpulkan lebih awal daripada kesan daripada getter. Jika a ialah Change, kesan fungsi kemas kini akan dilaksanakan terlebih dahulu, kemudian apabila fungsi render mengakses computed.value, ia akan mendapati bahawa _dirty masih palsu, kerana kesan getter belum dilaksanakan, jadi ia masih akan menjadi nilai lama pada masa ini. Pemprosesan ini dalam vue3 ialah apabila melaksanakan kesan, kesan yang sepadan dengan pengiraan akan dilaksanakan terlebih dahulu (juga disebut dalam bab sebelumnya):

    // packages/reactivity/src/effect.ts
    export function triggerEffects(
      dep: Dep | ReactiveEffect[],
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      // spread into array for stabilization
      const effects = isArray(dep) ? dep : [...dep]
      // computed的effect会先执行
      // 防止render获取computed值得时候_dirty还没有置为true
      for (const effect of effects) {
        if (effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
      for (const effect of effects) {
        if (!effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
    }
    Salin selepas log masuk

    watch

    watch lebih mudah daripada dikira, kerana Dia hanya perlu mewujudkan hubungan antara getter dan data responsif, dan apabila data responsif berubah, panggil panggilan balik yang diluluskan oleh pengguna dan masukkan nilai lama dan baharu

    // packages/runtime-core/src/apiWatch.ts
    export function watch<T = any, Immediate extends Readonly<boolean> = false>(
      source: T | WatchSource<T>,
      cb: any,
      options?: WatchOptions<Immediate>
    ): WatchStopHandle {
      if (__DEV__ && !isFunction(cb)) {
        warn(...)
      }
      // watch 具体实现
      return doWatch(source as any, cb, options)
    }
    Salin selepas log masuk
    function doWatch(
      source: WatchSource | WatchSource[] | WatchEffect | object,
      cb: WatchCallback | null,
      { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
    ): WatchStopHandle {
      if (__DEV__ && !cb) {
        ...
      }
      const warnInvalidSource = (s: unknown) => {
        warn(...)
      }
      const instance =
        getCurrentScope() === currentInstance?.scope ? currentInstance : null
      // const instance = currentInstance
      let getter: () => any
      let forceTrigger = false
      let isMultiSource = false
      // 根据不同source 创建不同的getter函数
      // getter 函数与computed的getter函数作用类似
      if (isRef(source)) {
        getter = () => source.value
        forceTrigger = isShallow(source)
      } else if (isReactive(source)) {
        // source是reactive对象时 自动开启deep=true
        getter = () => source
        deep = true
      } else if (isArray(source)) {
        isMultiSource = true
        forceTrigger = source.some(s => isReactive(s) || isShallow(s))
        getter = () =>
          source.map(s => {
            if (isRef(s)) {
              return s.value
            } else if (isReactive(s)) {
              return traverse(s)
            } else if (isFunction(s)) {
              return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
            } else {
              __DEV__ && warnInvalidSource(s)
            }
          })
      } else if (isFunction(source)) {
        if (cb) {
          // getter with cb
          getter = () =>
            callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
        } else {
          // no cb -> simple effect
          getter = () => {
            if (instance && instance.isUnmounted) {
              return
            }
            if (cleanup) {
              cleanup()
            }
            return callWithAsyncErrorHandling(
              source,
              instance,
              ErrorCodes.WATCH_CALLBACK,
              [onCleanup]
            )
          }
        }
      } else {
        getter = NOOP
        __DEV__ && warnInvalidSource(source)
      }
      // 2.x array mutation watch compat
      // 兼容vue2
      if (__COMPAT__ && cb && !deep) {
        const baseGetter = getter
        getter = () => {
          const val = baseGetter()
          if (
            isArray(val) &&
            checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
          ) {
            traverse(val)
          }
          return val
        }
      }
      // 深度监听
      if (cb && deep) {
        const baseGetter = getter
        // traverse会递归遍历对象的所有属性 以达到深度监听的目的
        getter = () => traverse(baseGetter())
      }
      let cleanup: () => void
      // watch回调的第三个参数 可以用此注册一个cleanup函数 会在下一次watch cb调用前执行
      // 常用于竞态问题的处理
      let onCleanup: OnCleanup = (fn: () => void) => {
        cleanup = effect.onStop = () => {
          callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
        }
      }
      // in SSR there is no need to setup an actual effect, and it should be noop
      // unless it&#39;s eager or sync flush
      let ssrCleanup: (() => void)[] | undefined
      if (__SSR__ && isInSSRComponentSetup) {
        // ssr处理 ...
      }
      // oldValue 声明 多个source监听则初始化为数组
      let oldValue: any = isMultiSource
        ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
        : INITIAL_WATCHER_VALUE
      // 调度器调用时执行
      const job: SchedulerJob = () => {
        if (!effect.active) {
          return
        }
        if (cb) {
          // watch(source, cb)
          // 获取newValue
          const newValue = effect.run()
          if (
            deep ||
            forceTrigger ||
            (isMultiSource
              ? (newValue as any[]).some((v, i) =>
                  hasChanged(v, (oldValue as any[])[i])
                )
              : hasChanged(newValue, oldValue)) ||
            (__COMPAT__ &&
              isArray(newValue) &&
              isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
          ) {
            // cleanup before running cb again
            if (cleanup) {
              // 执行onCleanup传过来的函数
              cleanup()
            }
            // 调用cb 参数为newValue、oldValue、onCleanup
            callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
              newValue,
              // pass undefined as the old value when it&#39;s changed for the first time
              oldValue === INITIAL_WATCHER_VALUE
                ? undefined
                : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
                ? []
                : oldValue,
              onCleanup
            ])
            // 更新oldValue
            oldValue = newValue
          }
        } else {
          // watchEffect
          effect.run()
        }
      }
      // important: mark the job as a watcher callback so that scheduler knows
      // it is allowed to self-trigger (#1727)
      job.allowRecurse = !!cb
      let scheduler: EffectScheduler
      if (flush === &#39;sync&#39;) {
        // 同步更新 即每次响应式数据改变都会回调一次cb 通常不使用
        scheduler = job as any // the scheduler function gets called directly
      } else if (flush === &#39;post&#39;) {
        // job放入pendingPostFlushCbs队列中
        // pendingPostFlushCbs队列会在queue队列执行完毕后执行 函数更新effect通常会放在queue队列中
        // 所以pendingPostFlushCbs队列执行时组件已经更新完毕
        scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
      } else {
        // default: &#39;pre&#39;
        job.pre = true
        if (instance) job.id = instance.uid
        // 默认异步更新 关于异步更新会和nextTick放在一起详细讲解
        scheduler = () => queueJob(job)
      }
      // 创建effect effect.run的时候建立effect与getter内响应式数据的关系
      const effect = new ReactiveEffect(getter, scheduler)
      if (__DEV__) {
        effect.onTrack = onTrack
        effect.onTrigger = onTrigger
      }
      // initial run
      if (cb) {
        if (immediate) {
          // 立马执行一次job
          job()
        } else {
          // 否则执行effect.run() 会执行getter 获取oldValue
          oldValue = effect.run()
        }
      } else if (flush === &#39;post&#39;) {
        queuePostRenderEffect(
          effect.run.bind(effect),
          instance && instance.suspense
        )
      } else {
        effect.run()
      }
      // 返回一个取消监听的函数
      const unwatch = () => {
        effect.stop()
        if (instance && instance.scope) {
          remove(instance.scope.effects!, effect)
        }
      }
      if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
      return unwatch
    }
    Salin selepas log masuk

    Atas ialah kandungan terperinci Vue3 dikira dan menonton analisis kod sumber. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Label berkaitan:
    sumber:yisu.com
    Kenyataan Laman Web ini
    Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
    Tutorial Popular
    Lagi>
    Muat turun terkini
    Lagi>
    kesan web
    Kod sumber laman web
    Bahan laman web
    Templat hujung hadapan