「Vue3学习篇」-isProxy()、toRaw()、markRaw()

『引言』

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

『isProxy()』

『定义』

【官方解释】 检查一个对象是否是由 reactive()readonly()shallowReactive()shallowReadonly() 创建的代理。

『用法』

语法: isProxy()

『示例🌰』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{hobbies.join('、')}}</p>
    <p>地址:{{address.provice}} - {{ address.city }} </p>
    <p>最喜欢的颜色:{{favoriteColor.coloeOne}}&{{ favoriteColor.colorTwo }}</p>
    <p>描述:{{description}}</p>
    <button @click="judgeIsProxy">
      isProxy判断
    </button>
  </div> 
</template>

<script setup>
import { ref, reactive, readonly,  shallowRef, shallowReactive, shallowReadonly, isProxy } from 'vue'
    
    const age = ref(3)
    const name = ref('wnxx')
    const address = reactive({
          provice: '浙江省',
          city: '杭州市'        
      })
    const newNname = readonly(name)
    const hobbies = shallowRef(['打羽毛球', '旅游'])
    const favoriteColor = shallowReactive({
      coloeOne: '薄荷绿',
      colorTwo: '天蓝色'
    })
    const description = ref('非常的可爱,特别喜欢吃蜂蜜!')
    const newDescription = shallowReadonly(description)
    
    const judgeIsProxy = () => {
        console.log(isProxy(name), '判断ref姓名') 
        console.log(isProxy(newNname), '判断readonly姓名') 
        console.log(isProxy(age), '判断ref年龄')
        console.log(isProxy(hobbies), '判断shallowRef爱好') 
        console.log(isProxy(address), '判断reactive地址') 
        console.log(isProxy(favoriteColor), '判断shallowReactive颜色') 
        console.log(isProxy(description), '判断reactive描述') 
        console.log(isProxy(newDescription), '判断shallowReadonly描述') 
    }
</script>

『效果展示』

『isProxy源码』

isProxy源码如下⬇️:

scss 复制代码
export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

可以看到分别调用isReactiveisReadonly方法。

先看看isReactive源码。

scss 复制代码
export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

首先调用isReadonly,判断是否是readonly API创建的对象

然后是由readonly api创建的话,就通过调用isReactive,对当前对象的RAW属性进行判断

最后判断readonly源对象是否为reactive不是的话,就通过判断__v_isReactive是否为true,返回最后结果。

  • ReactiveFlags.RAW是一个字符串,值为:__v_raw
  • ReactiveFlags.IS_READONLY也是一个字符串,值为:__v_isReactive

再看看isReadonly源码。

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

『toRaw()』

『定义』

【官方解释】 根据一个 Vue 创建的代理返回其原始对象。

  • 详细信息

    toRaw() 可以返回由 reactive()readonly()shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

    这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

【我的理解】 toRaw 方法可以将一个由reactive生成的响应式对象转为普通对象。这样数据已经不再是响应式的数据了。修改普通对象,数据会改变,但视图不会更新。

『用法』

语法: toRaw()

『官网示例🌰』

arduino 复制代码
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

『注意』

  • toRaw对ref无效。
  • 可以帮助我们避免某些情况下意外触发了响应式更新或影响了性能等问题。
  • 使用toRaw包裹的reactive赋值给一个变量 修改这个变量的时候原reactive对象的值还是会互相影响的。
  • ref和reactive 数据每次修改都会被追踪,都会更新UI界面,也是非常消耗性能的,所以如果我们有一些操作不需要追踪,不需要更新UI界面,那么这个时候我们就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改,虽然数据改变了,但是这样不会被追踪,也就不会更新UI界面,这样会优化一些性能。

『示例🌰:对reactive数据的操作』

xml 复制代码
<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">
      修改原响应式数据
    </button>
    <button @click="modifyNewInfo">
      修改toRaw得到的原始数据
    </button>
  </div> 
</template>

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

『效果展示』

『代码解析』

上面代码中,使用reactive包裹了一个info对象,然后使用toRaw()将使用reactive包裹的info转换为普通对象,最后对数据进行修改。

在对数据进行修改的时候,列举了两种情况。

『第一种:修改原响应式数据』

修改原响应式数据info时,toRaw 转换得到的数据会被修改,视图也会更新。

『第二种:修改 toRaw 得到的原始数据』

如果修改toRaw得到的原始数据newInfo,原数据也会被修改,但是视图不更新。

『答疑』

提问 🚩:为什么修改newInfo数据时,视图不更新?

答案 📒:newInfo的对象类型为Object,它不是一个响应式的,所以修改无效。

『示例🌰:对ref数据的操作』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{hobbies.join('、')}}</p>
    <p>描述:{{description}}</p>
    <button @click="modifyInfo">
      修改原响应式数据
    </button>
    <button @click="modifyNewInfo">
      修改toRaw得到的原始数据
    </button>
  </div> 
</template>

<script setup>
import { ref, toRaw } from 'vue'
    
    const name = ref('pupu')
    const age = ref(10)
    const hobbies = ref(['唱歌', '画画'])
    const description = ref('一点也不可爱,不喜欢吃蜂蜜!')
   
    const nameInfo = toRaw(name)
    const ageInfo = toRaw(age)
    const hobbiesInfo = toRaw(hobbies)
    const descriptionInfo = toRaw(description)
    console.log(name,age,hobbies,description, 'info')
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        hobbies.value = ['打羽毛球', '旅游']
        description.value = '非常的可爱,特别喜欢吃蜂蜜!'
        console.log(name,age,hobbies,description, 'info改后')
        console.log(nameInfo,ageInfo,hobbiesInfo,descriptionInfo, 'Info')
    } 
    
    const modifyNewInfo = () => {
        nameInfo.value = 'pupu'
        ageInfo.value = 10 
        hobbiesInfo.value = ['唱歌', '画画']
        descriptionInfo.value = '一点也不可爱,不喜欢吃蜂蜜!'
        console.log(nameInfo,ageInfo,hobbiesInfo,descriptionInfo, 'Info改后')
        console.log(name,age,hobbies,description, 'info改后')
    }
</script>

『效果展示』

『代码解析』

上面代码中,使用ref定义数据,然后使用toRaw()将使用ref定义的数据转为普通对象,最后对数据进行修改。

同样的,在对数据进行修改的时候,列举了两种情况。

『第一种:修改原响应式数据』

修改原响应式数据时,toRaw 转换得到的数据会被修改,视图也会更新。

『第二种:修改 toRaw 得到的原始数据』

如果修改 toRaw 得到的原始数据,原数据也会被修改,视图也会更新。

由此可知,toRaw 对ref数据无效。

『toRaw源码』

toRaw源码如下⬇️:

csharp 复制代码
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}
  • ReactiveFlags.RAW是一个字符串,值为:__v_raw。
  • 返回传入参数的__v_raw属性值或者直接原值返回。

『markRaw()』

『定义』

【官方解释】 将一个对象标记为不可被转为代理。返回该对象本身。

  • 谨慎使用

    markRaw() 和类似 shallowReactive() 这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:

    • 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
    • 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。

    这应该是一种进阶需求,因为只在根层访问能到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:

    ini 复制代码
    const foo = markRaw({
      nested: {}
    })
    
    const bar = reactive({
      // 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
      nested: foo.nested
    })
    
    console.log(foo.nested === bar.nested) // false

    识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。

【我的理解】 markRaw()用于标记一个对象,使其永远不再成为一个响应式对象,当修改这个值的时候还是可以改变但是界面不会更新。方法返回值为对象本身。

『用法』

语法: markRaw()

『官网示例🌰』

scss 复制代码
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

『示例🌰』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p v-if="info.address">地址:{{info.address.provice}} - {{ info.address.city }} </p>
    <p>描述:{{info.description}}</p>
    <button @click="updateInfo">
      更新info数据
    </button>
    <button @click="modifyInfo">
      修改info数据
    </button>
    <button @click="modifyAddress">
      修改地址数据
    </button>
  </div> 
</template>

<script setup>
import {reactive, markRaw } from 'vue'
    
    const info = reactive({
      name: 'pupu',
      age: 10,
      hobbies: ['唱歌', '画画'],
      description: '一点也不可爱,不喜欢吃蜂蜜!'
    })

    const updateInfo = () => {
      const address = {
        provice: '浙江省',
        city: '杭州市'        
      }
      info.address = markRaw(address)
    }    
   
    const modifyInfo = () => {
        info.name = 'wnxx'
        info.age = 3 
        info.hobbies = ['打羽毛球', '旅游']
        info.description = '非常的可爱,特别喜欢吃蜂蜜!'
        console.log(info, 'modifyInfo')
    }
    
    const modifyAddress = () => {
        info.address.provice = '云南省'
        info.address.city = '丽江市'
        console.log(info, 'modifyAddress')
    }
</script>

『效果展示』

『代码解析』

使用reactive包裹了info对象,对数据进行了三次操作,分别是更新info数据,修改info数据,修改address数据。

『第一次:更新info数据』

新添加一个地址的属性,然后使用markRaw()将地址数据包裹起来。当点击『更新info数据按钮🔘』时,可以看到视图更新了。

『第二次:修改info数据』

第二次操作是对已经更新的info数据进行修改,当点击『修改info数据按钮🔘』时,同样可以看到视图也更新了,控制台打印的数据也更新了。

『第三次:修改address数据』

第三次次操作是对使用markRaw()的地址数据进行修改,当点击『修改address数据按钮🔘』时,清晰地看到视图并未更新,数据是更新的。

『markRaw源码』

toRaw源码如下⬇️:

typescript 复制代码
export function markRaw<T extends object>(value: T): Raw<T> {
  def(value, ReactiveFlags.SKIP, true)
  return value
}

可以看到markRaw内部调用def方法,传入接受参数value, __v_skip,true3个参数,并返回传入参数值。

markRaw通过增加一个属性标志ReactiveFlags.SKIP,来判断是否能进行reactive。

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

判断如果target有ReactiveFlags.SKIP属性,则直接判断,不在可以进行reactive的白名单中,从而不能进行reactive。

来看一下def方法源码。

def 定义对象属性

typescript 复制代码
export const def = (obj: object, key: string | symbol, value: any) => {
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: false,
    value
  })
}

『参数说明』:

value: 当试图获取属性时所返回的值。
enumerable: 该属性在for in循环中是否会被枚举。
configurable: 该属性是否可被删除。

def方法通过Object.defineProperty修改传入对象的配置属性和可枚举属性。 Object.defineProperty 算是一个非常重要的API。这里先不做详细介绍。

相关推荐
everyStudy37 分钟前
前端五种排序
前端·算法·排序算法
甜兒.2 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
Jiaberrr5 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy6 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白6 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、6 小时前
Web Worker 简单使用
前端
web_learning_3216 小时前
信息收集常用指令
前端·搜索引擎
tabzzz6 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
LvManBa6 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust
200不是二百6 小时前
Vuex详解
前端·javascript·vue.js