赞
踩
在vue2中data配置
中的属性会通过Object.defineProperty
原理最终包装成响应式数据_data。
vue3中为我们提供了两种包装响应式数据的方法:ref和reactive
注意这里的ref和vue2中的ref不一样,这里是一个ref函数
。
上面使用setup包裹页面数据,但是这样编写出的数据不是响应式的,即数据改变页面不会被重新加载。从vue2中我们知道一个数据要实现响应式一定有对应的响应式得getter和setter方法(实现原理是Object.defineProperty
),在vue3中通过ref函数
帮我们生成响应式的getter和setter:
// 伪代码,不是真正的实现
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
ref的实现原理也是通过Object.defineProperty
实现的
定义一个响应式的数据
import { ref } from 'vue'
const xxx = ref(数据)
(创建一个包含响应式数据的引用对象(reference对象,简称ref对象)).value
属性上变量名.value
不需要.value
,直接:<div>{{变量名}}</div>
例子:
<template> <h1>app组件</h1> <h2>姓名{{ name }}</h2> <h2>年龄{{ age }}</h2> <button @click="sayHello">hello</button> <button @click="changeInfo">修改人的信息</button> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { // 数据 let name = ref('yang') let age = ref(18) // 方法 function sayHello() { alert(`你好呀,我叫${name.value}`) } function changeInfo() { console.log(name) name.value = 'cheng', age.value = 20 } return { name, age, sayHello, changeInfo } } } </script>
ref当然也可以包装对象,那就有一个问题。他是否会包装对象中的数据,让对象中的数据也成为响应式的,这样对象中的数据改变才能重新渲染页面。
答案是:对象中的数据vue也帮我们设置成了响应式的但是不是通过ref实现的,是通过Proxy
(ES6语法)实现的。(——vue3中封装了reactive()函数
来实现Proxy
)
所以获取ref包装的对象的属性时: 对象.value.属性
(属性后面无需再加value了,属性不是用ref封装的)
eg:app.vue
<template> <h1>app组件</h1> <h2>姓名{{ name }}</h2> <h2>年龄{{ age }}</h2> <h2>工作种类{{ job.type }}</h2> <h2>薪资{{ job.salary }}</h2> <button @click="sayHello">hello</button> <br /> <button @click="changeInfo">修改人的信息</button> <button @click="changeJob">修改工作信息</button> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { // 数据 let name = ref('yang') let age = ref(18) let job = ref({ type: '前端工程师', salary:'30k' }) // 方法 function sayHello() { alert(`你好呀,我叫${name.value}`) } function changeInfo() { console.log(name) name.value = 'cheng', age.value = 20 } function changeJob() { job.value.type = 'UI设计师' job.value.salary = '100k' } return { name, age, job, sayHello, changeInfo, changeJob } } } </script>
我们将ref可以实现对象的属性的响应性叫做ref的深层响应性。
同时我们可以放弃ref的深层响应性,通过shallow ref
实现,对于浅层的ref只有.value的访问会被追踪。浅层的ref可以于避免对大型数据的响应性开销来优化性能。
eg:
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
shallowRef的数据类型:
function shallowRef<T>(value: T): ShallowRef<T>
interface ShallowRef<T> {
value: T
}
object.defineProperty()
的get 与set
完成的。object.defineProperty()
的get 与set
,内部“求助”了Vue3.0中的一个新函数——reactive函数
。const count = ref(0)
const object = { id: ref(1) }
//因此,这个表达式按预期工作:
{{ count + 1 }}
//...但这个不会:
{{ object.id + 1 }}
object.id
的时候我们可以先解构 id,然后再模板中使用{{ id + 1 }}
.value
进行取值,在模板中使用{{object.id.value+1}}
const { id } = object
//使用
{{ id + 1 }}
ref 是文本插值的最终计算值
(即 {{ }} 标签),那么它将被解包,因此以下内容将渲染为 1{{ object.id }}
上述写法等价于 {{ object.id.value }}
作用:定义一个对象类型
的响应式数据(基本类型不要用它,要用ref函数)
const 代理对象 = reactive(源对象)
let job = reactive({
type: '前端工程师',
salary:'30k'
})
console.log(job)
例子:
app.vue
<template> <h1>app组件</h1> <h2>姓名{{ name }}</h2> <h2>年龄{{ age }}</h2> <h2>工作种类{{ job.type }}</h2> <h2>薪资{{ job.salary }}</h2> <h2>c:{{ job.a.b.c }}</h2> <button @click="sayHello">hello</button> <br /> <button @click="changeInfo">修改人的信息</button> <button @click="changeJob">修改工作信息</button> </template> <script> import { ref,reactive } from 'vue' export default { name: 'App', setup() { // 数据 let name = ref('yang') let age = ref(18) let job = reactive({ type: '前端工程师', salary: '30k', a:{ b: { c:6 } } }) // 方法 function sayHello() { alert(`你好呀,我叫${name.value}`) } function changeInfo() { console.log(name) name.value = 'cheng', age.value = 20 } function changeJob() { // console.log(job) job.type = 'UI设计师' job.salary = '100k' job.a.b.c = 666 } return { name, age, job, sayHello, changeInfo, changeJob } } } </script>
reactive定义的响应式数据是“深层次的”。
Proxy封装的数组,可以直接通过下标修改数据,同时实现响应式布局。
定义数据
let hobby = reactive(['吃饭', '睡觉', '打豆豆'])
修改数据
function changeHobby() {
hobby[0] = 'study'
}
当数组数据改变可以引起页面重新渲染
app.vue:
<template> <h1>app组件</h1> <h2>姓名{{ person.name }}</h2> <h2>年龄{{ person.age }}</h2> <h2>工作种类{{ person.job.type }}</h2> <h2>薪资{{ person.job.salary }}</h2> <h2>c:{{ person.job.a.b.c }}</h2> <h3>hobby:{{ person.hobby}}</h3> <button @click="changeInfo">修改人的信息</button> </template> <script> import { ref,reactive } from 'vue' export default { name: 'App', setup() { let person = reactive({ name: 'yang', age: 18, job: { type: '前端工程师', salary: '30k', a: { b: { c: 6 } } }, hobby: ['吃饭', '睡觉', '打豆豆'] }) function changeInfo() { person.name = 'cheng', person.age = 18, person.job.type = 'UI设计师' person.job.salary = '100k' person.job.a.b.c = 666 person.hobby[0] = 'study' } return { person, changeInfo } } } </script>
Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。reactive() 将深层地
转换对象:当访问嵌套对象时,它们也会被 reactive()
包装。与浅层 ref 类似,这里也有一个 shallowReactive()
API 可以选择退出深层响应,浅层的reactive只会对第一层对象实现响应式:
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++
shallowReactive()
和 reactive()
不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包
了(需要使用.value进行访问,并且值为ref的属性的响应式不会消失)。
直接源数据封装成Proxy代理对象(ES6语法中的代理对象),Proxy代理对象是响应式的。
reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的,为保证访问代理的一致性:
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
依靠深层响应性,响应式对象内的嵌套对象依然是代理,默认添加reactive
实现响应式:
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
对象类型
(对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型
。let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (赋值一个新的响应式对象,但页面上连接的是原来的state对象,之前的state对象连接被断开,所以不会触发响应式)
state = reactive({ count: 1 })
//不会触发响应式,响应性连接已丢失
state.count++
//赋值普通对象响应性连接丢失
state = { count: 3 }
// 不会触发响应式,响应性连接已丢失
state.count++
一个 ref 会在作为响应式对象的属性被访问或修改时自动解包,无需使用.value
访问值。
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
当 ref 作为reactive的数组或原生集合类型
(如 Map) 中的元素被访问时,它不会被解包
:
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
当修改了响应式状态时,DOM 会被自动更新。但是DOM 更新不是同步的。
Vue 会在“next tick”更新周期
中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 现在 DOM 已经更新了
}
object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。<script type='text/javascript'> let person ={ name:'yang', age:18 } // vue2的响应式原理 let p ={} Object.defineProperty(p,'name',{ get(){ return person.name }, set(value){ console.log("name被修改了,触发了响应式") person.name = value } }) Object.defineProperty(p,'age',{ get(){ return person.age }, set(value){ console.log("age被修改了,触发了响应式") person.age = value } }) </script>
this.$set(this.person, 'sex','女')
this.$delete(this.person, 'sex'')
this.$set(this.person.hobby, 0,'学习')
this.person.hobby.splice(0,1,'学习')
vue2中存在的问题在vue3中都解决了:
(1)新增属性、删除属性,界面会更新。
(2)直接通过下标修改数组,界面会自动更新。
eg:实现添加sex属性和删除name属性
<template> <h1>app组件</h1> <h2 v-show="person.name">姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <h2 v-show="person.sex">性别:{{ person.sex }}</h2> <h2>工作种类:{{ person.job.type }}</h2> <h2>薪资:{{ person.job.salary }}</h2> <h2>c:{{ person.job.a.b.c }}</h2> <h3>hobby:{{ person.hobby}}</h3> <button @click="changeInfo">修改人的信息</button> <button @click="addsex">添加一个sex属性</button> <button @click="deleteName">删除一个name属性</button> </template> <script> import {reactive } from 'vue' export default { name: 'App', setup() { //数据 let person = reactive({ name: 'yang', age: 18, job: { type: '前端工程师', salary: '30k', a: { b: { c: 6 } } }, hobby: ['吃饭', '睡觉', '打豆豆'] }) // 方法 function changeInfo() { person.name = 'cheng', person.age = 18, person.job.type = 'UI设计师' person.job.salary = '100k' person.job.a.b.c = 666 person.hobby[0] = 'study' } function addsex() { person.sex = '女' } function deleteName (){ delete person.name } return { person, changeInfo, addsex, deleteName } } } </script>
通过Proxy
实现,Proxy
是es6中的语法,是window身上的一个内置函数window.Proxy
,可以直接使用
proxy
的意思是代理,
const p =new Proxy(person,{})
增删改
变化都可以被检测到(和Object.defineProperty不同之处,Object.defineProperty只能检测到改
的变化)。增删改
操作的响应式<script type='text/javascript'> let person ={ name:'yang', age:18 } // vue3的响应式原理 const p =new Proxy(person,{ // 读取p的属性的响应式 get(target,propName){ // target代表person源对象 // propName代表读取的属性名 console.log(`有人读取了person身上的${propName}属性`) // propName是一个变量需要使用数组形式读取 return target[propName] }, // 修改p或给p追加属性时的响应式 set(target,propName,value){ console.log(`有人修改了了person身上的${propName}属性,我要去修改页面了`) target[propName] = value }, // 删除p的属性时的响应式 deleteProperty(target,propName){ console.log(`有人删除了person身上的${propName}属性,我要去修改页面了`) return delete target[propName] } }) </script>
Reflect也是ES6新增的一个属性,在Window身上,可以直接调用。
let person ={
name:'yang',
age:18
}
// 读取
Reflect.get(person,"name")
// 修改
Reflect.set(person,"name","cheng")
// 添加
Reflect.set(person,"sex","男")
// 删除
Reflect.deleteProperty(person,"name")
let person ={ name:'yang', age:18 } Reflect.defineProperty(person,"school",{ get(){ return "nefu" }, set(value){ person.school = value } }) //Object写法 /*Object.defineProperty(person,"school",{ get(){ return "nefu" }, set(value){ person.school = value } })*/
Reflect.defineProperty 和 Object.defineProperty的区别:
Object.defineProperty
对一个代理对象设置两个相同的属性会直接报错。
Reflect.defineProperty
对一个代理对象设置两个相同的属性不会报错,且以第一次设置的属性为准。
<script type='text/javascript'> let person ={ name:'yang', age:18 } // vue3的响应式原理 const p =new Proxy(person,{ // 读取p的属性的响应式 get(target,propName){ // target代表person源对象 // propName代表读取的属性名 console.log(`有人读取了person身上的${propName}属性`) // propName是一个变量需要使用数组形式读取 return Reflect.get(target,propName) }, // 修改或追加p属性时的响应式 set(target,propName,value){ console.log(`有人修改了了person身上的${propName}属性,我要去修改页面了`) Reflect.set(target,propName,value) }, // 删除p的属性时的响应式 deleteProperty(target,propName){ console.log(`有人删除了person身上的${propName}属性,我要去修改页面了`) return Reflect.deleteProperty(target,propName) } }) </script>
vue3的响应式原理就是通过Proxy代理对象
和 Reflect反射对象
实现的
ref
用来定义:基本类型
数据,但是ref也可以定义对象数据类型。reactive
用来定义对象(或数组)
类型数据,且reactive不能用来定义基本数据类型。备注: ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive
转为代理对象。
object.defineProperty()
的get与set来实现响应式(数据劫持)。Proxy
来实现响应式(数据劫持),并通过Reflect
操作源对象内部的数据。其实ref底层还是调用的reactive。
.value
,模板中读取数据时直接读取不需要.value 。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。