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

Penjelasan terperinci tentang perbezaan antara reaktif dan ref dalam vue3 (analisis kod sumber)

青灯夜游
Lepaskan: 2022-08-22 19:53:30
ke hadapan
3202 orang telah melayarinya

Apakah perbezaan antara reaktif dan ref dalam

vue? Artikel berikut akan membawa anda jauh ke dalam kod sumber untuk memahami dengan teliti perbezaan antara reaktif dan ref dalam vue3. Saya harap ia akan membantu anda!

Penjelasan terperinci tentang perbezaan antara reaktif dan ref dalam vue3 (analisis kod sumber)

Dalam perkembangan harian vue3, saya mendapati ramai orang menggunakan reactive atau ref ulang-alik berdasarkan tabiat mereka sendiri. Dalam kes ini, mengapa kita perlu mereka bentuk reactive yang lain sedangkan kita sudah mempunyai ref? Apakah senario aplikasi dan perbezaan sebenar antara kedua-duanya?

Dan mengenai logik asas ref, sesetengah orang mengatakan bahawa logik asas ref masih reactive. Sesetengah orang mengatakan bahawa lapisan bawah ref ialah class, dan value hanyalah atribut class ini yang manakah betul? Adakah terdapat asas untuk ini?

Dengan memikirkan soalan ini, kami akan mendalami kod sumber kali ini dan memahami dengan teliti perbezaan antara reactive dan ref dalam vue3. (Belajar perkongsian video: tutorial video vue)

Jika anda tidak mahu melihat kod sumber, anda boleh tatal ke belakang untuk melihat ringkasan

reaktif

Alamat kod sumber: packages/reactivity/reactive.ts

Pertama, mari kita lihat vue3targetReactiveFlags untuk menandakan objek sasaran

taip >
// 标记目标对象 target 类型的 ReactiveFlags
export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean          // 不做响应式处理的数据
  [ReactiveFlags.IS_REACTIVE]?: boolean   // target 是否是响应式
  [ReactiveFlags.IS_READONLY]?: boolean   // target 是否是只读
  [ReactiveFlags.RAW]?: any               // 表示proxy 对应的源数据, target 已经是 proxy 对象时会有该属性
}
Salin selepas log masuk

reactive

export function reactive<t>(target: T): UnwrapNestedRefs<t>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果目标对象是一个只读的响应数据,则直接返回目标对象
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 创建 observe
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}</t></t>
Salin selepas log masuk

Fungsi menerima reactive objek, dan jika target objek baca sahaja, ia mengembalikan objek secara langsung Jika objek target

bukan baca sahaja, cipta objek

terus melalui createReactiveObject dan. tontonnya Jangan takut panjang, mula-mula siarkan observe kod lengkap, mari baca di bahagian

Mula-mula kita lihat menerima lima parameter createReactiveObject

objek sasaran sasaran

createReactiveObject

/**
 * 
 * @param target 目标对象
 * @param isReadonly 是否只读
 * @param baseHandlers 基本类型的 handlers
 * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers
 * @param proxyMap  WeakMap数据结构
 * @returns 
 */

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<target>
) {

  // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,
  // 通过target能判断出是否有ReactiveFlags.RAW属性)
  // 例外:对reactive对象进行readonly()
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  // 只对targetTypeMap类型白名单中的类型进行响应式处理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // proxy 代理 target
  // (set、map、weakSet、weakMap) collectionHandlers
  // (Object、Array) baseHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}</target></any></any>
Salin selepas log masuk
isReadonly Sama ada ia dibaca sahaja

createReactiveObject

  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<target></target></any></any>
Salin selepas log masuk
baseHandlers Pengendali jenis asas

mengendalikan tatasusunan, objek

collectionHandlers

Set proses, peta, weakSet, weakMap

proxyMap

Struktur data WeakMap menyimpan fungsi kesan sampingan

Di sini terutamanya melalui

dan Tentukan sama ada ia adalah data responsif Jika ya, teruskan kembali objek

, terus keluarkan objek Proksi daripada struktur data dan mengembalikannya


ReactiveFlags.RAWDi sini kita menyemak sama ada jenis ReactiveFlags.IS_REACTIVE semasa ialah

,
 if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
Salin selepas log masuk
,
. ,

, Proxy, WeakMap , jika tidak demikian, objek akan dikembalikan terus tanpa pemprosesan responsif

  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
Salin selepas log masuk

Logik jenis pengesahan

targetObjectArraySetelah semua pra-pengesahan selesai, Anda boleh menggunakan Map untuk proksi Set objek WeakMapWeakSetDi sini kami menggunakan

untuk melaksanakan logik pemprosesan yang berbeza melalui
 // 只对targetTypeMap类型白名单中的类型进行响应式处理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
Salin selepas log masuk

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}
Salin selepas log masuk
(set, peta, weakSet , weakMap) Gunakan

proxy(Objek, Tatasusunan) Gunakan target

三目运算符TargetType.COLLECTION

Sekarang ialah pelaksanaan logik
    sangat jelas? Di sini kami menggunakan
  • sebagai contoh dan masuk jauh ke dalam collectionHandlers untuk melihat
  • baseHandlers
// proxy 代理 target
  // (set、map、weakSet、weakMap) collectionHandlers
  // (Object、Array) baseHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
Salin selepas log masuk

createReactiveObject Kod sumber alamat:

createReactiveObjectDalam proxy kita dapat lihat bahawa sejumlah empat pengendali telah diperkenalkan targetbaseHandlersbaseHandlers

Pemprosesan pembolehubah baseHandlers Baca sahaja Pemprosesan

Pemprosesan cetek (hanya perhatikan atribut peringkat pertama objek sasaran)

packages/reactivity/baseHandlers.ts

Pemerhatian cetek&& baca sahaja

reactive.ts

Kami menggunakan
import {
  mutableHandlers,
  readonlyHandlers,
  shallowReactiveHandlers,
  shallowReadonlyHandlers
} from './baseHandlers'
Salin selepas log masuk
Sebagai contoh
  • mutableHandlersDi sini
  • dan
  • sepadan dengan readonlyHandlers,
  • shallowReactiveHandlers
  • shallowReadonlyHandlerscreateGetter() masing-masing

mutableHandlers

Mula-mula pergi ke versi penuh kod
// 可变处理
// const get = /*#__PURE__*/ createGetter()
// const set = /*#__PURE__*/ createSetter()
// get、has、ownKeys 会触发依赖收集 track()
// set、deleteProperty 会触发更新 trigger()
export const mutableHandlers: ProxyHandler<object> = {
  get,                  // 用于拦截对象的读取属性操作
  set,                  // 用于拦截对象的设置属性操作
  deleteProperty,       // 用于拦截对象的删除属性操作
  has,                  // 检查一个对象是否拥有某个属性
  ownKeys               // 针对 getOwnPropertyNames,  getOwnPropertySymbols, keys 的代理方法
}</object>
Salin selepas log masuk

getIa kelihatan panjang, dan akhirnya adalah set Koleksi ketergantungancreateGetter()createSetter()

    Terdapat terlalu banyak koleksi kandungan, jadi bersama-sama dengan
  • mencetuskan kemas kini, buka artikel berasingan

createSetter()

/**
 * 拦截对象的设置属性操作
 * @param shallow 是否是浅观察
 * @returns 
 */
function createSetter(shallow = false) {
  /**
   * @param target 目标对象
   * @param key 设置的属性名称
   * @param value 要改变的属性值
   * @param receiver 如果遇到setter,receiver则为setter调用时的this值
   */
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    // 如果模式不是浅观察模式
    if (!shallow) {
      // 拿新值和老值的原始值,因为新传入的值可能是响应式数据,如果直接和 target 上原始值比较是没有意义的
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 目标对象不是数组,旧值是ref,新值不是ref,则直接赋值,这里提到ref
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    // 检查对象是否有这个属性
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) <p><code>trigger()</code>触发更新</p><h2 data-id="heading-7"><strong>ref</strong></h2><p>源码地址:<code>packages/reactivity/src/ref.ts</code></p><p>接收一个可选<code>unknown</code>,接着直接调用<code>createRef()</code></p><pre class="brush:php;toolbar:false">export function ref(value?: unknown) {
  return createRef(value, false)
}
Salin selepas log masuk

Penjelasan terperinci tentang perbezaan antara reaktif dan ref dalam vue3 (analisis kod sumber)

ref的区别就是在调用createRef()时第二个值传的是true

export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
Salin selepas log masuk

看一下官方文档上对shallowRef的解释

Penjelasan terperinci tentang perbezaan antara reaktif dan ref dalam vue3 (analisis kod sumber)

createRef

通过isRef()判断是否是ref数据,是则直接返回该数据,不是则通过new RefImpl创建ref数据

在创建时会传两个值一个是rawValue(原始值),一个是shallow(是否是浅观察),具体使用场景可看上面refshallowRef的介绍

function createRef(rawValue: unknown, shallow: boolean) {
  // 是否是 ref 数据
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
Salin selepas log masuk
  • isRef()

通过__v_isRef只读属性判断是否是ref数据,此属性会在RefImpl创建ref数据时添加

export function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}
Salin selepas log masuk

RefImpl

class RefImpl<t> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  // 只读属性 __v_isRef 判断是否是ref数据的静态标识
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    this._rawValue = _shallow ? value : toRaw(value)  // 非浅观察用toRaw()包裹原始值
    this._value = _shallow ? value : toReactive(value) // 非浅观察用toReactive()处理数据
  }

  get value() {
  // 依赖收集
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal) // 非浅观察用toRaw()包裹值
    // 两个值不相等
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal) // 触发依赖,派发更新
    }
  }
}</t>
Salin selepas log masuk

根据RefImpl我们可以看到ref的底层逻辑,如果是对象确实会使用reactive进行处理,并且ref的创建使用的也是RefImpl class实例,value只是RefImpl的属性

在我们访问设置 ref的value值时,也分别是通过getset拦截进行依赖收集派发更新

  • toReactive

我们来看一下toReactive()这个方法,在RefImpl中创建ref数据时会调用toReactive()方法,这里会先判断传进来的值是不是对象,如果是就用reactive()包裹,否则就返回其本身

export const toReactive = <t>(value: T): T =>
  isObject(value) ? reactive(value) : value</t>
Salin selepas log masuk
  • trackRefValue

ref的依赖收集方法

export function trackRefValue(ref: RefBase<any>) {
  if (isTracking()) {
    ref = toRaw(ref)
    if (!ref.dep) {
      ref.dep = createDep()
    }
    if (__DEV__) {
      trackEffects(ref.dep, {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep)
    }
  }
}</any>
Salin selepas log masuk
  • triggerRefValue

ref的派发更新方法

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(ref.dep)
    }
  }
}</any>
Salin selepas log masuk

总结

看完reactiveref源码,相信对本文一开始的几个问题也都有了答案,这里也总结了几个问题:

  • 问:ref的底层逻辑是什么,具体是如何实现的

答:ref底层会通过 new RefImpl()来创造ref数据,在new RefImpl()会首先给数据添加__v_isRef只读属性用来标识ref数据。而后判断传入的值是否是对象,如果是对象则使用toReactive()处理成reactive,并将值赋给RefImpl()value属性上。在访问设置ref数据的value时会分别触发依赖收集派发更新流程。


  • 问:ref底层是否会使用reactive处理数据

答:RefImpl中非浅观察会调用toReactive()方法处理数据,toReactive()中会先判断传入的值是不是一个对象,如果是对象则使用reactive进行处理,不是则直接返回值本身。


  • 问:为什么已经有了reactive还需要在设计一个ref呢?

答: 因为vue3响应式方案使用的是proxy,而proxy的代理目标必须是非原始值,没有任何方式能去拦截对原始值的操作,所以就需要一层对象作为包裹,间接实现原始值的响应式方案。


  • 问:为什么ref数据必须要有个value属性,访问ref数据必须要通过.value的方式呢?

答:这是因为要解决响应式丢失的问题,举个例子:

// obj是响应式数据
const obj = reactive({ foo: 1, bar: 2 })

// newObj 对象下具有与 obj对象同名的属性,并且每个属性值都是一个对象
// 该对象具有一个访问器属性 value,当读取 value的值时,其实读取的是 obj 对象下相应的属性值 
const newObj = {
    foo: {
        get value() {
            return obj.foo
        }
    },
    bar: {
        get value() {
            return obj.bar
        }
    }
}

effect(() => {
    // 在副作用函数内通过新对象 newObj 读取 foo 的属性值
    console.log(newObj.foo)
})
// 正常触发响应
obj.foo = 100
Salin selepas log masuk

可以看到,在现在的newObj对象下,具有与obj对象同名的属性,而且每个属性的值都是一个对象,例如foo 属性的值是:

{
    get value() {
        return obj.foo
    }
}
Salin selepas log masuk

该对象有一个访问器属性value,当读取value的值时,最终读取的是响应式数据obj下的同名属性值。也就是说,当在副作用函数内读取newObj.foo时,等价于间接读取了obj.foo的值。这样响应式数据就能够与副作用函数建立响应联系

(Belajar perkongsian video: pembangunan bahagian hadapan web, Video pengaturcaraan asas)

Atas ialah kandungan terperinci Penjelasan terperinci tentang perbezaan antara reaktif dan ref dalam vue3 (analisis kod sumber). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:juejin.cn
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
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!