Vue3响应式对象Reactive和Ref的用法解读

目录
  • 一、内容简介
  • 二、Reactive
    • 1. 关键源码
    • 2. 源码流程分析
  • 三、代理拦截操作
    • 1. 数组操作
    • 2.Get操作
    • 3. Set操作
    • 4. 其余行为拦截操作
  • 四、Ref对象
    • 1. 思考一个问题
    • 2. 简要说明
    • 3. 关键源码
  • 四. 源码解析
  • 五、总结

一、内容简介

本篇文章着重结合源码版本V3.2.20介绍Reactive和Ref。前置技能需要了解Proxy对象的工作机制,以下贴出的源码均在关键位置备注了详细注释。

备注:本篇幅只讲到收集依赖和触发依赖更新的时机,并未讲到如何收集依赖和如何触发依赖。响应式原理快捷通道。

二、Reactive

1. 关键源码

/*源码位置:/packages/reactivity/src/reactive.ts*/
/**
 * 创建响应式代理对象
 * @param target 被代理对象
 * @param isReadonly 是否只读
 * @param baseHandlers 普通对象的拦截操作
 * @param collectionHandlers 集合对象的拦截操作
 * @param proxyMap 代理Map
 * @returns 
 */
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  //如果不是对象,则警告,Proxy代理只支持对象
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  //如果被代理对象已经是一个proxy对象且是响应式的并且此次创建的新代理对象不是只读的,则直接返回被代理对象
  //这儿存在一种情况需要重新创建,即被代理对象已经是一个代理对象了,且可读可写。但新创建的代理对象是只读的
  //那么,本次生成的那个代理对象最终是只读的。响应式必须可读可写,只读的代理对象是非响应式的。
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  //从map中找,如果对象已经被代理过,则直接从map中返回,否则生成代理
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 获取代理类型,即采用集合类型的代理还是普通对象类型的代理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 生成代理对象并存入map中
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

2. 源码流程分析

Vue中创建响应式代理对象都是通过createReactiveObject方法创建。这个方法里面的主要逻辑很简单,就是生成一个目标对象的代理对象,代理对象最为核心的操作拦截则由外部根据是否只读和是否浅响应传入,然后将这个代理对象存起来以备下次快捷获取。

三、代理拦截操作

1. 数组操作

(1).关键源码

//源码位置: /packages/reactivity/src/baseHandlers.ts
function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      //获取原始数组
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        //收集依赖 键值为索引 i
        track(arr, TrackOpTypes.GET, i + '')
      }
      // 调用数组的原始方法
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // 如果不存在,则将参数参数转换为原始数据在试一次(这儿可能是防止传入的是代理对象导致获取失败)
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      //由于上面的方法会改变数组长度,因此暂停收集依赖,不然会导致无限递归
      pauseTracking()
      //调用原始方法
      const res = (toRaw(this) as any)[key].apply(this, args)
      //复原依赖收集
      resetTracking()
      return res
    }
  })
  return instrumentations
}

(2).源码流程分析

上述源码其实就是重写了对于数组方法的操作,在通过数组的代理对象访问以上数组方法时,就会执行重写后的数组方法。

内部逻辑很简单,对于改变了数组长度的方法,先暂停依赖收集,调用原始数组方法,然后复原依赖收集。

对于判断元素是否存在的数组方法,执行依赖收集并调用数组原始方法。

总结来说最终都是调用了数组的原始方法,只不过在调用前后添加了关于依赖收集相关的行为。

2.Get操作

(1).关键源码

//源码位置: /packages/reactivity/src/baseHandlers.ts
/**
 * 创建并且返回一个Get方法
 * @param isReadonly 是否只读
 * @param shallow 是否浅响应
 * @returns 
 */
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    //这儿不重要,其实就是通过代理对象访问这几个特殊属性时,返回相应的值,和响应式无关
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }
  
    const targetIsArray = isArray(target)
    //如果是调用的数组方法,则调用重写后的数组方法,前提不是只读的
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    //调用原始行为获取值
    const res = Reflect.get(target, key, receiver)
    //访问Symbol对象上的属性和__proto__,__v_isRef,__isVue这3个属性,直接返回结果值
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    if (!isReadonly) {
      //不是只读,则收集依赖
      track(target, TrackOpTypes.GET, key)
    }
    if (shallow) {
      //如果对象是浅响应的 则返回结果
      return res
    }
    if (isRef(res)) {
      //如果值是Ref对象且是通过数组代理对象的下标访问的,则不做解包装操作,否则返回解包装后的值
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
    if (isObject(res)) {
      //走到这儿需要满足非浅响应。如果结果是一个对象,则将改对象转换为只读代理对象或者响应式代理对象返回
      //e.g. 
      // test:{
      //   a:{
      //     c:10
      //   }
      // }
      //以上测试对象当访问属性a时,此时res是一个普通对象,如果不转换为代理对象,则对a.c的操作不会被拦截处理,导致无法响应式处理
      // 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.
      return isReadonly ? readonly(res) : reactive(res)
    }
    
    return res
  }
}

(2).源码流程分析

上述Get方法是在通过代理对象获取某一个值时触发的。流程很简单,就是对几个特殊属性做了特殊返回。

如果是数组方法,则调用重写后的数组方法,不是则调用原始行为获取值。

如果不是只读,则收集依赖,对返回结果进行判断特殊处理。其中最关键的地方在于收集依赖和将获取到的嵌套对象转换为响应式对象。

3. Set操作

(1).关键源码

//源码位置: /packages/reactivity/src/baseHandlers.ts
/**
 * 创建并返回一个Set方法
 * @param shallow 是否浅响应
 * @returns 
 */
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    //获取改变之前的值
    let oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      //对Ref类型值的特殊处理
      //比较2个值,如果旧值是Ref对象,新值不是,则直接变Ref对象的value属性
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        //这儿看似没有触发依赖更新,其实Ref对象的value进行赋值会触发Ref对象的写操作,在那个操作里面会触发依赖更新
        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) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 这个判断其实是处理一个代理对象的原型也是代理对象的情况,以下是测试代码
    // let hiddenValue: any
    // const obj = reactive<{ prop?: number }>({})
    // const parent = reactive({
    //   set prop(value) {
    //     hiddenValue = value
    //   },
    //   get prop() {
    //     return hiddenValue
    //   }
    // })
    // Object.setPrototypeOf(obj, parent)
    // obj.prop = 4
    // 当存在上述情形,第一次设置值时,由于子代理没有prop属性方法,会触发父代理的set方法。父代理的这个判断此时是false,算是一个优化,避免2个触发更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        //触发add类型依赖更新
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        //触发set类型依赖更新
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

(2).源码流程分析

当设置时,首先对旧值是Ref类型对象做了个特殊处理,如果满足条件,则走Ref对象的set方法逻辑触发依赖更新。

否则根据是否存在key值,判断是新增属性,还是修改属性,触发不同类型的依赖更新。

之所以要区分依赖类型,是因为某些属性会连带别的属性更改,比如数组直接设置下标,会导致length的更改,这个时候需要收集length为键值的依赖,以便连带更新依赖的length属性的地方。

4. 其余行为拦截操作

(1).关键源码

//源码位置: /packages/reactivity/src/baseHandlers.ts
/**
 * delete操作符时触发
 * @param target 目标对象
 * @param key 键值
 * @returns 
 */
function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    //触发依赖更新
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}
/**
 * in 操作符时触发
 * @param target 目标对象
 * @param key 键值
 * @returns 
 */
function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
      //收集依赖
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}
/**
 * Object.keys()等类似方法时调用
 * @param target 目标对象
 * @returns 
 */
function ownKeys(target: object): (string | symbol)[] {
  //收集依赖
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

(2).源码流程分析

上述源码其实就是在对一些特殊操作符或者特定API时的特殊处理,本质还是收集依赖和触发依赖更新,没什么好讲的。

四、Ref对象

1. 思考一个问题

为什么存在了Reactive代理对象后,已经可以进行依赖收集和依赖更新了,还要设计一个Ref类型。

测试一:针对以下测试代码,Ref对象值的改变正确触发了更新

    //源码位置 /packages/reactivity/__test__/ref.spec.ts
    const a = ref(1)
    let dummy
    let calls = 0
    effect(() => {
      calls++
      dummy = a.value;
    })
    a.value = 2;
    //此时dummy = 2,a对象值的改变触发了依赖更新

测试二:修改以上代码,更新失败

    const a = 1
    let dummy
    let calls = 0
    effect(() => {
      calls++
      dummy = a;
    })
    a= 2;
    //此时dummy = 1,a的改变没有触发依赖更新

上述2个示例很明显的表明出了,对于非响应式对象的改变,不会触发依赖更新。Reactive是通过代理实现的,代理只支持对象,不支持非对象的基础类型。所以需要设计一个Ref类型来包装这些类型数据,以便拥有响应式状态

2. 简要说明

既然设计了Ref来支持非对象属性,那么也一定需要兼容对象属性。内部其实很简单,如果是对象,则直接转为Reactive代理对象。

3. 关键源码

class RefImpl<T> {
  private _value: T
  private _rawValue: T
  public dep?: Dep = undefined
  public readonly __v_isRef = true
  constructor(value: T, public readonly _shallow: boolean) {
    //原始数据
    this._rawValue = _shallow ? value : toRaw(value)
    //外部访问到的数据,转换为响应式
    this._value = _shallow ? value : toReactive(value)
  }
  get value() {
    //跟踪依赖
    trackRefValue(this)
    return this._value
  }
  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      //如果原始数据之间的比较不一样,则赋值
      this._rawValue = newVal
      //把新值转换为响应式对象
      this._value = this._shallow ? newVal : toReactive(newVal)
      //触发依赖
      triggerRefValue(this, newVal)
    }
  }
}
//转换响应式对象方法
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value
type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}
//收集依赖
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)
    }
  }
}
//触发依赖更新
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)
    }
  }
}
/**
 * 自定义响应式对象
 */
class CustomRefImpl<T> {
  public dep?: Dep = undefined
  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']
  public readonly __v_isRef = true
  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    )
    this._get = get
    this._set = set
  }
  get value() {
    return this._get()
  }
  set value(newVal) {
    this._set(newVal)
  }
}

四. 源码解析

Ref对象实际是代理的简化版,针对value设置了一个getter,setter读取器。

这个读取器可以对读写操作进行拦截,因此可以进行依赖的收集和更新。

同时又巧妙了对reactive做了一层封装,假如传入的是一个多层嵌套的复杂对象,最终是类似ref.value.a其实操作的已经是reactive代理对象上的属性,已经和ref无关了。对于CustomRefImpl类型,其实核心和RefImpl是一样的,更加精简,只不过将Get方法和Set方法交给程序员自己去实现了。

只需要在这个Get方法里面调用track方法进行依赖收集和在Set方法里面调用依赖更新即可。

示例代码如下:

    let value = 1
    const custom = customRef((track, trigger) => ({
      get() {
        track()
        return value
      },
      set(newValue: number) {
        value = newValue
        trigger()
      }
    }))
    let dummy
    effect(() => {
      dummy = custom.value 
    })
    custom.value = 2
    //此时dummy = 2;

五、总结

1. 收集依赖和触发依赖的本质

export const enum TrackOpTypes {
  GET = 'get',
  HAS = 'has',
  ITERATE = 'iterate'
}
export const enum TriggerOpTypes {
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear'
}

以上时源码中定义的收集依赖的和触发依赖的类型。其实也就是当涉及读操作时收集依赖,当设计写操作时触发依赖更新。

2. 响应式对象本质是对数据进行了包装,拦截了读写操作。

3. 上述篇幅并未讲到集合类型代理的处理,原理其实一样,有兴趣的可以自行翻阅源码。

4. 本篇幅只讲到收集依赖和触发依赖的时机,并未讲到如何收集和如何触发。

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • vue对象的深度克隆方式

    目录 vue对象的深度克隆 方法1 方法2 方法3 vue克隆对象时遇到的问题 vue对象的深度克隆 方法1 通过js序列化,将js转换成字符串,然后再将字符串转换成js对象 var olbObj = {a:1}; var str = JSON.stringify(obj); //序列化对象 var newobj = JSON.parse(str); //还原 //相当于 var newObj = JSON.parse(JSON.stringify(olbObj )) 方法2 ES6语法对象展开

  • vue如何查找数组中符合条件的对象

    目录 查找数组中符合条件的对象 根据id找出数组里的对象 查找数组中符合条件的对象 let val = 1; let list = [ {id:1,name:'张三'}, {id:2,name:'李四'}, {id:3,name:'王五'}, ]; let arr = list.filter((i) => { return val == i.id; }); 根据id找出数组里的对象  dsChange(id){       let selectedName = {};       selecte

  • vue3接口数据赋值对象,渲染报错问题及解决

    目录 vue3接口数据赋值对象,渲染报错 vue在渲染数据的时候的一些报错问题 问题描述 解决方法 vue3接口数据赋值对象,渲染报错 const app = require('express')() // 跨域设置 app.all("*", function (req, res, next) { res.setHeader("Access-Control-Allow-Credentials", true); res.setHeader("Access-C

  • Vue关于对象直接赋值的坑及解决

    目录 Vue对象直接赋值的坑 Vue对象的赋值Object.assign({}, row) Vue对象直接赋值的坑 受JavaScript的限制,Vue不能监听到对象属性的添加或删除.如果这样做了,你会发现添加或删除的属性不是响应式的,即无法及时更新视图. 如果我们需要将a对象赋值给b对象时,要么把b对象的所有属性都在a对象中定义一下,要么 使用this.$set(this.object,key,value)将b对象中有a对象中没有的属性set进去,或者使用Object.assign(this.

  • vue对象复制方式(深拷贝,多层对象拷贝方式在后面)

    目录 vue对象复制 更新 我的理解 vue对象复制的坑--对象深度拷贝 错误描述 解决办法 vue对象复制 使用:es6中的“对象扩展运算符 ”,如下 // 对象深拷贝 obejctCopy() { // 源对象小李 const source = { name: '小李', age: 18, gender: '男', school: '清华大学' } // 拷贝小李 const copy1 = { ...source } // 拷贝小李,并修改名字为小张 const copy2 = { ...

  • vue路由传参接收以及传参对象为对象时的问题及解决

    目录 路由传参接收以及传参对象为对象时的问题 场景 接收路由参数 vue路由传参总结 Vue路由传参 路由传参接收以及传参对象为对象时的问题 具体代码如下所示: 场景 <div @click='toDetail'>查看详情</div> 路由传参不能直接传一个对象,需要使用JSON.stringify()方法将其转换成一个字符串,然后在其他页面接受的时候再使用JSON.parse()方法转换成一个对象 const router = useRouter() const toDetail

  • Vue3响应式对象Reactive和Ref的用法解读

    目录 一.内容简介 二.Reactive 1. 关键源码 2. 源码流程分析 三.代理拦截操作 1. 数组操作 2.Get操作 3. Set操作 4. 其余行为拦截操作 四.Ref对象 1. 思考一个问题 2. 简要说明 3. 关键源码 四. 源码解析 五.总结 一.内容简介 本篇文章着重结合源码版本V3.2.20介绍Reactive和Ref.前置技能需要了解Proxy对象的工作机制,以下贴出的源码均在关键位置备注了详细注释. 备注:本篇幅只讲到收集依赖和触发依赖更新的时机,并未讲到如何收集依赖

  • vue3 响应式对象如何实现方法的不同点

    目录 vue3响应式对象实现方法的不同点 Vue2和Vue3响应式原理对比 响应式原理实现逻辑 Vue2响应式原理简化 Vue2响应式原理弊端 Vue3响应式原理简化 vue3响应式对象实现方法的不同点 vue 响应式对象是利用 defindeProperty 实现,vue3 则是使用 Proxy 来实现响应式的. 二者,虽然都实现了 响应式的功能,但实现方式不一样,解决问题也有些不同. vue2 中,如果想要实现,数组元素的更新,则是需要 用到检测更新的方法 set 之类的,但 vue3的响应

  • vue3常用响应式对象的api,你全用过了吗

    目录 Ⅰ. ref.reactive ( 递归监听 ) Ⅱ. isRef.isReactive ( 判断 ) Ⅲ. toRef 和 toRefs ( 解构 ) Ⅳ. toRaw . markRaw ( 解除代理) Ⅴ. unref ( 拷贝 ) Ⅵ. shallowRef .shallowReactive( 非递归监听 ) Ⅶ. triggerRef (强制更新) 总结 Ⅰ. ref.reactive ( 递归监听 ) import {ref,reactive} from 'vue'; setu

  • Vue3响应式对象是如何实现的(2)

    目录 前言 分支切换的优化 副作用函数嵌套产生的BUG 自增/自减操作产生的BUG 前言 在Vue3响应式对象是如何实现的(1)中,我们已经从功能上实现了一个响应式对象.如果仅仅满足于功能实现,我们就可以止步于此了.但在上篇中,我们仅考虑了最简单的情况,想要完成一个完整可用的响应式,需要我们继续对细节深入思考.在特定场景下,是否存在BUG?是否还能继续优化? 分支切换的优化 在上篇中,收集副作用函数是利用get自动收集.那么被get自动收集的副作用函数,是否有可能会产生多余的触发呢?或者说,我们

  • setup+ref+reactive实现vue3响应式功能

    setup 是用来写组合式 api ,内部的数据和方法需要通过 return 之后,模板才能使用.在之前 vue2 中,data 返回的数据,可以直接进行双向绑定使用,如果我们把 setup 中数据类型直接双向绑定,发现变量并不能实时响应.接下来就看看setup如何实现data的响应式功能? 一.ref setup 内的自定义属性不具备响应式能力,所以引入了 ref ,ref 底层通过代理,把属性包装值包装成一个 proxy ,proxy 内部是一个对象,使得基础类型的数据具备响应式能力,使用之

  • Vue3关于响应式数据类型详解(ref、reactive、toRef、及toRefs)

    目录 ref reactive toRef() toRefs() ref 接受一个内部值,返回一个响应式的.可更改的 ref 对象,此对象只有一个指向其内部值的 property .value. 类型 function ref<T>(value: T): Ref<UnwrapRef<T>> interface Ref<T> { value: T } 详细信息 ref 对象是可更改的,也就是说你可以为 .value 赋予新的值.它也是响应式的,即所有对 .va

  • Vue3响应式方案及ref reactive的区别详解

    目录 一.前言 二.新的方案 1. 缘由 2. Proxy 和 Reflect 1) Proxy 2) Reflect 3. reactive 1) createReactiveObject() 函数 2) mutableHandlers() 函数 -> 对象类型的 handlers 3) mutableInstrumentations() 函数 -> Map Set等类型的 handlers 4. ref 1) createRef() 2) toReactive() 3)proxyRefs(

  • vue3响应式Object代理对象的读取示例详解

    目录 正文 读取属性 xx in obj for ... in 正文 从这一章开始,作者将更新深入的讲解响应式,尤其是vue3响应式的具体的实现.其实在前面一章,如果你仔细阅读,你是可以实现一个简单的响应式函数的,类似于@vue/reactive,当然那只是个demo,是个玩具,我能不能在生产环境上去使用的,它差了太多功能和边界条件. 现在,我们才是真正的深入@vue/reactive. 在vue中,obj.a是一个读取操作,但是仔细想来,读取这个操作很宽泛. obj.a // 访问一个属性 '

  • Vue3 响应式侦听与计算的实现

    响应式侦听和计算 有时我们需要依赖于其他状态的状态--在 Vue 中,这是用组件 计算属性 处理的,以直接创建计算值,我们可以使用 computed 方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象. 我们先来看看一个简单的例子,关于计算值的方式,同样我们在 src/TemplateM.vue 写下如下代码: <template> <div class="template-m-wrap"> count --->

  • 浅析vue3响应式数据与watch属性

    是Vue3的 composition API中2个最重要的响应式API ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式) 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 ref内部: 通过给value属性添加getter/setter来实现对数据的劫持 reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据 ref的数据操作: 在js中要.value, 在模板中不需要(内部解析

随机推荐