likes
comments
collection
share

「Vue3学习篇」-readonly()、isReadonly() 、shallowReadonly()

作者站长头像
站长
· 阅读数 41

『引言』

本篇介绍readonly()、isReadonly()和shallowReadonly()这三个API。关于这三个API是用来干什么的,以及使用方法等等都会有说明。

『readonly()』

『定义』

【官方解释】 接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

  • 详细信息

    只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

【我的理解】 readonly()可以接受『响应式对象、纯对象、ref』,返回原始对象的只读代理。

  • 这个只读代理是深层的,任何被访问的嵌套属性也是只读的。与reactive()一样,如果任何属性使用了ref,当它通过代理访问时,则被自动解包。

『用法』

语法: readonly()

『官网示例🌰』

const original = reactive({ count: 0 })

// 常量copy是只读的,original值发生变化时,copy也会变化。
const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

『注意⚠️』

  • 使用readonly()将数据变为只读的数据后,数据就不可以修改了。
  • 如果要是强制修改的话,控制台会报警告信息
  • 使用readonly()可以避免错误的操作增加代码的安全性和可维护性

『示例🌰』

<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{ info.address.city }} </p>
    <p>描述:{{description}}</p>
    <button @click="Modifyname">
      修改姓名
    </button>
    <button @click="Modifydescription">
      修改描述
    </button>
  </div> 
</template>

<script setup>
import { ref, reactive, readonly } from 'vue'

    const info = reactive({
      name: 'pupu',
      age: 10,
      hobbies: ['唱歌', '画画'],
      address: {
        provice: '浙江省',
        city: '杭州市'        
      }
    })
      const description = ref('一点也不可爱,不喜欢吃蜂蜜!')
      const myReadonlyInfo = readonly(info)
      const myReadonlydescription = readonly(description)
    
    const Modifyname = () => {
        myReadonlyInfo.name = 'wnxx'      
    }
    
    const Modifydescription = () => {
        myReadonlydescription.value = '非常的可爱,特别喜欢吃蜂蜜'
    } 
</script>

『效果展示』

「Vue3学习篇」-readonly()、isReadonly() 、shallowReadonly()

『代码解析』

上面示例中,使用reactive定义了一个info对象,使用ref定义了一个description,然后使用readonly()方法返回一个只读对象。

当点击修改姓名和修改描述按钮🔘时,会发现控制台会报出警告⚠️信息:[Vue warn] Set operation on key "xxx" failed: target is readonly.

与此同时,视图也并未发生改变,姓名和描述都无法修改

『readonly源码』

这个就是readonly源码,可以看到内部调用createReactiveObject函数

export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}

『参数说明』:

  • 第一个参数target。
  • 第二个参数是isReadonly,为true。
  • 第三个参数readonlyHandlers是readonly对应的处理函数。
  • 第四个参数是对于集合类型的对象进行处理的readonly所对应的函数。

再来看一下createReactiveObject函数源码。

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  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
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  // 获取target的类型,如果其为INVALID,说明不能进行代理,所以直接返回target
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 根据target生成对应的proxy对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  //将代理对象缓存在对应proxyMap中
  proxyMap.set(target, proxy)
  return proxy
}

createReactiveObject主要是判断target的类型,然后通过判断看是直接返回target,还是返回一个新建的proxy

createReactiveObject方法的最后一个参数proxyMapreadonly创建的weakMap用来缓存代理对象。

createReactiveObject根据普通对象和数组类型、Set和Map类型来区分baseHandlerscollectionHandlers

  • baseHandlers处理数组,对象类型。
  • collectionHandlers处理Map、Set、WeakMap类型。

🙋🙋‍♂️提问🚩:

什么情况下会直接返回🔙target,那又是什么情况下返回🔙一个新建的proxy❓🤔🤔

回答📒:

直接返回🔙target的情况

  1. target不是对象。
  2. target已经是个proxy对象
  3. target类型校验不合法。

返回🔙一个新建的proxy的情况

当参数proxyMap对应的实参里已经存在了target的响应式对象时,直接取出并返回该响应式对象。

否则,创建一个target的响应式对象proxy,将proxy加入到proxyMap中,然后返回该proxy

createReactiveObject函数小结

如果不是对象,即不是Object,Array,Map,Set,WeakMap,WeakSet类型的,就不可以代理,在开发模式抛出警告,生产环境直接返回目标对象。

已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为过程中会用weakMap进行保存,通过target能判断出是否有ReactiveFlags.RAW属性)。 例外:对reactive对象进行readonly()。

如果target已经存在对应的代理对象,则直接从WeakMap数据结构中取出这个Proxy对象(对应的代理对象)并返回。

只对targetTypeMap类型白名单中的类型进行响应式处理。

『isReadonly()』

『定义』

【官方解释】 检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。

通过 readonly() 和 shallowReadonly() 创建的代理都是只读的,因为他们是没有 set 函数的 computed() ref。

【我的理解】 isReadonly()用于判断对象是否是由readonly()shallowReadonly()创建的只读代理。

『用法』

语法:isReadonly()

只有readonlyshallowReadonly建立的代理才会返回true,其他的都是false。

『示例🌰』

<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{address.provice}} - {{ address.city }} </p>
    <p>描述:{{description}}</p>
    <button @click="isJudgeIsReadonly">
      isReadonly判断
    </button>
  </div> 
</template>

<script setup>
import { ref, readonly, shallowReadonly, isReadonly } from 'vue'

    const info = readonly({
      name: 'pupu',
      age: 10,
      hobbies: ['唱歌', '画画'],
    })
    const address = shallowReadonly({
        provice: '浙江省',
        city: '杭州市'        
    })
    const description = ref('一点也不可爱,不喜欢吃蜂蜜!')
    
    const  isJudgeIsReadonly  = () => {
        console.log('判断info', isReadonly(info)) 
        console.log('判断address', isReadonly(address))
        console.log('判断description', isReadonly(description))
    }
</script>

『效果展示』

「Vue3学习篇」-readonly()、isReadonly() 、shallowReadonly()

『代码解析』

上面代码中,使用readonly包裹了一个info,使用shallowReadonly包裹了一个address,最后使用ref定义了description。

通过isReadonly()来判断,可以在控制台打印的信息中看到前两者返回true,后者返回的是false。

『isReadonly源码』

isReadonly源码如下⬇️:

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
  • ReactiveFlags.IS_READONLY是一个字符串,值为:__v_isReadonly
  • 判断当前对象的__v_isReadonly属性是否是true,并返回。

『shallowReadonly()』

『定义』

【官方解释】 readonly() 的浅层作用形式。

  • 详细信息

    和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

    ‼️谨慎使用

    浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。

【我的理解】 shallowReadonly()用于创建一个proxy,自身的属性为只读,但深层数据不受影响。

『用法』

语法:shallowReadonly()

『官网示例🌰』

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性会失败
state.foo++

// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false

// 这是可以通过的
state.nested.bar++

『注意』

  • 只有第一层数据变成只读,不可修改,深层数据可修改
  • 修改第一层数据,控制台会报错警告信息

『示例🌰』

<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{ info.address.city }} </p>
    <p>描述:{{info.description}}</p>
    <button @click="ModifyInfo">
      shallowReadonly修改
    </button>
  </div> 
</template>

<script setup>
import { reactive, shallowReadonly } from 'vue'

    const info = reactive({
      name: 'wnxx',
      age: 3,
      hobbies: ['打羽毛球', '旅游'],
      address:{
          provice: '浙江省',
          city: '杭州市'        
      },
      description:  '非常的可爱,特别喜欢吃蜂蜜!'
    })
    const newInfo = shallowReadonly(info)
    
    const ModifyInfo = () => {
        newInfo.name = 'pupu'
        newInfo.age = 10
        newInfo.hobbies = ['唱歌', '画画'] 
        newInfo.address.provice = '云南省'   
        newInfo.address.city = '丽江市'
        newInfo.description = '一点也不可爱,不喜欢吃蜂蜜!'
        console.log(newInfo, 'newInfo') 
    }
</script>

『效果展示』

「Vue3学习篇」-readonly()、isReadonly() 、shallowReadonly()

『代码解析』

可以看到修改newInfo.name、newInfo.age等第一层数据时,控制台会报出警告信息:[Vue warn] Set operation on key "XXX" failed: target is readonly.

当修改newInfo.address.provice和newInfo.address.city深层数据时,视图会改变,数据也可以修改。

『shallowReadonly源码』

shallowReadonly源码如下⬇️:

export function shallowReadonly<T extends object>(target: T): Readonly<T> {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  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
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

shallowReadonly源码中,在入口函数中,有几个参数。

  • 第一个参数还是target;
  • 第二个是isReadonly,为true;
  • 第三个参数shallowReadonlyHandlers是shallowReadonly对应的处理函数;
  • 第四个参数是对于集合类型的对象进行处理的shallowReadonly所对应的函数。

然后就是与reactive和readonly一样,都是调用的createReactiveObject函数。

转载自:https://juejin.cn/post/7269045227087527970
评论
请登录