vue 3.x响应式基础

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

返回的是一个代理对象,以后的操作都通过代理对象进行。这样就可以在每次访问和赋值的时候进行某些操作

是深度拦截的:嵌套的对象,新建的属性等都会进行代理

Vue中当使用 Composition API 显式创建响应式对象时,最佳做法是不要保留对原始对象的引用,而只使用响应式版本

  • Proxy基本用法
const obj = { name: 'wmm', age: 18 } const proxy = new Proxy(obj, { get(target, key) { console.log('get: ', key) return target[key] }, set(target, key, value) { console.log('set: ', key, value) target[key] = value } }) // 读取或者写入的时候,使用代理对象 // 使用原始对象的话,不会打印出 get: 或者 set: console.log(proxy.name) // wmm // 原对象和代理对象是不一样的 console.log(proxy === obj) // false proxy.name = 'wmm66' console.log(obj) // { name: 'wmm66', age: 18 }

使用Reflect的方法,可以让我们正确的执行this绑定

proxy中的getter中跟踪更改它的函数

proxy中的setter中触发函数trigger以便视图等可以更新最终值

const obj = { name: 'wmm', age: 18 } const proxy = new Proxy(obj, { get(target, key) { // 跟踪更改它的函数 track(target, key) return Reflect.get(...arguments) }, set(target, key, value) { // 触发函数以便它可以更新最终值 trigger(target, key) return Reflect.set(...arguments) } })

Vue 在内部跟踪所有已被设置为响应式的对象,因此它始终会返回同一个对象的 proxy 版本。

从响应式 proxy 访问嵌套对象时,该对象在返回之前也被转换为 proxy

const handler = { get(target, prop, receiver) { track(target, prop) const value = Reflect.get(...arguments) if (isObject(value)) { return reactive(value) } else { return value } } // ... }

常用API

  • ref(value):将值value转成响应式对象
  • reactive(obj):返回对象的响应式副本
  • readonly(obj | proxy):返回一个只读的proxy对象
  • isProxy(proxy):对象是否是由 reactivereadonly 创建的 proxy
  • isReactive(proxy):对象是否是 reactive创建的响应式 proxy
  • isReadonly(proxy):对象是否是由readonly创建的只读 proxy
  • isRef(proxy):是否是ref对象
  • toRef(proxy, key):为原响应式对象上的property新创建一个ref。保持对其源 property 的响应式连接
  • toRefs(proxy):将响应式对象转换为普通对象。结果对象的每个 property 都是指向原始对象相应 propertyref。常用于批量解构并保持响应性

ref

有一个独立的原始值,把它变成响应式的,可以使用ref()

返回一个可变的响应式对象,该对象包含一个名为value的属性。访问和修改值可以借助该属性

<template> <div> <!-- 直接访问name即可,setup中的ref会自动展开。不是 name.value --> {{ name }} <button @click="nameAdd">修改name值</button> </div> </template> <script> import { ref } from 'vue' export default { setup() { const name = ref('wmm') // 响应式的 console.log(name) // 读取值(访问.value属性) console.log(name.value) // wmm const nameAdd = () => { // 设置值(修改.value属性的值) name.value += '6' } return { // return出去的需要是响应式的数据,这样数据有修改,视图才能对应的更新 // name是响应式的, name.value不是响应式的(只是一个字符串) // 所以这里应该是 name: name 不能是 name: name.value name, // 相当于 name: name nameAdd } } } </script>

image.png

reactive

相当于vue 2.xVue.observable()

返回一个响应式的proxy对象

深度转换,影响嵌套对象传递的所有property

组件中的data()返回一个对象时,在内部就是交由reactive()使其成为响应式对象的

<template> <div> {{ person.name }} <button @click="nameAdd">修改name值</button> </div> </template> <script> import { reactive } from 'vue' export default { setup() { const obj = { name: 'wmm', age: 18 } // 返回的是一个代理对象。是一个响应式对象 const person = reactive(obj) console.log(person) // 代理对象跟原对象不同。一般用的是代理对象,代理对象是响应式的 console.log(person === obj) // false console.log(person.name) // wmm person.age++ // 修改了代理对象,原对象也会改变 console.log(person.age, obj.age) // 19 19 const nameAdd = () => { person.name += '6' } return { person, nameAdd } } } </script>

image.png

ref + reactive

import { ref, reactive } from 'vue' export default { setup() { const name = ref('wmm') const job = ref('fe') const fav = ref('basketball') const person = reactive({ name, age: 18 }) person.job = job // .job 是一个响应式对象 RefImpl person.fav = fav.value // .fav 是一个字符串 console.log(person) person.name += 66 person.job += 66 person.fav += 66 console.log(person.name, name.value) // wmm66 wmm66 console.log(person.job, job.value) // fe66 fe66 // .fav只是一个字符串,是基本数据类型 console.log(person.fav, fav.value) // basketball66 basketball } }

image.png

Ref 展开仅发生在被响应式 Object 嵌套的时候

当从 Array 或原生集合类型如Map访问 ref 时,不会进行展开

const books = reactive([ref('Vue 3 Guide')]) // 这里需要 .value console.log(books[0].value) // Vue 3 Guide const map = reactive(new Map([['count', ref(0)]])) // 这里需要 .value console.log(map.get('count').value) // 0

refreactive功能上有些重复,可能是为了方便两种编程习惯

// ref let x = 10 let y = 10 // reactive const obj = { x: 10, y: 10 }

响应式状态解构(toRefs、toRef)

import { ref, reactive, toRefs, toRef } from 'vue' export default { setup() { const oName = ref('wmm') const oJob = ref('fe') const oFav = ref('basketball') const person = reactive({ name: oName, age: 18 }) person.job = oJob person.fav = oFav.value // 直接解构得到的是解构出来的值,会丢失响应性 // const { name, age, job, fav } = person // console.log(name, age, job, fav) // wmm 18 fe basketball // 使用toRefs(proxy)批量解构 const { name, age, job, fav } = toRefs(person) console.log(name, age, job, fav) // 使用toRef(proxy, key)解构某个property const age1 = toRef(person, 'age') console.log(age1) age1.value++ console.log(person.age, age.value, age1.value) // 19 19 19 } }

image.png

readonly

基于一个对象(响应式或纯对象)或ref,返回原始proxy的只读proxy对象

是深层的,访问的任何嵌套property也是只读的

const person = reactive({ name: 'wmm', age: 18 }) const copy = readonly(person) console.log(copy) // proxy对象 person.age++ console.log(person.age, copy.age) // 19 19 copy.age++ // warn

image.png

isReactive

如果 proxyreadonly 创建的,但还包装了由 reactive 创建的另一个 proxy,它也会返回 true

const state = reactive({ name: 'wmm' }) // 从普通对象创建的只读 proxy const plain = readonly({ name: 'wmm66' }) console.log(isReactive(plain)) // false // 从响应式 proxy 创建的只读 proxy const stateCopy = readonly(state) console.log(isReactive(stateCopy)) // true

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: