一、数据劫持的实现
Vue2中使用的是Object.defineProperty
,想要实现深层对象的响应式,必须使用递归,但是性能不好,所以使用了$set
方法响应式添加属性
let obj1 = {
a: 1,
b: 2
}
let obj2 = {}
for (let item in obj1) {
Object.defineProperty(obj2, item, {
get() { return obj1[item] },
set() { }
})
}
console.log(obj2) // obj2中有了obj1的属性,且是响应式的
但是Vue3中使用的是new Proxy
实现响应式
let obj1 = {
a: 1,
b: 2
}
let obj2 = new Proxy(obj1, {
get() {},
set() {}
})
console.log(obj2)
二、常用组合式API
1、setup函数
什么是setup函数
- setup是一个新的配置项,值是一个函数
-
是所有组合式API表演的舞台
- 组件中所用到的:数据、方法等都要配置在setup中
<!-- 写法一 Vue2+Vue3-->
<template>
<h1>这是helloword组件</h1>
{{name}}
</template>
<script>
export default {
setup() {
let name = '张三'
return {
name
}
}
}
</script>
<!-- 写法二 纯Vue3-->
<template>
<h1>这是helloword组件</h1>
{{name}}
</template>
<script setup>
let name = '张三'
</script>
<style scoped>
</style>
2、ref函数
什么是ref函数
vue3中直接定义数据let a = 1
,数据是不可以修改的,只能展示在视图层
所以使用ref函数用于定义一个响应式数据
ref函数的使用
import { ref } from 'vue'
// 用法
const name = ref('张三')
- 创建一个包含响应式数据的引用对象(reference对象)
- 在js中读取数据:name.value
- 在模板中读取数据:直接name,不需要“点value”
备注:
- 接收的数据可以是基本类型、也可以是对象类型
- 基本类型数据:靠Object.defineProperty实现响应式
- 对象数据类型:内部使用了Vue3的新函数——reactive函数(封装了proxy方法)
const obj = ref({ name: '张三' })
// 读取属性
console.log(obj.value) // Proxy {name: '张三'}
// 只需要使用obj.value拿到代理对象,之后不再使用value获取
3、reactive函数
作用:定义一个对象类型的响应式数据
注意:基本数据类型使用的是ref函数实现响应式
使用方法
import { reactive } from 'vue'
// const 代理对象 = reactive(原对象)
const obj = reactive({name:'张三'})
console.log(obj) // Proxy {name: '张三'},是一个Proxy的实例对象
// 接收一个对象或者一个数组,返回的是代理对象,数据的修改可以被vue捕获到,是响应式的
- reactive定义的响应式数据是深层次的
- 都可以直接使用属性或者索引进行修改
- 基于ES6的Proxy实现,通过代理对象操作原对象内部数据是响应式的
4、ref和reactive对比
定义数据的角度
- ref定义基本类型数据,也可以定义对象/数组类型数据,内部通过reactive转换为代理对象
- reactive定义对象或者数组类型数据
从原理角度
- ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)
- reactive通过proxy实现响应式(数据劫持),并通过Reflect操作源对象中的数据
从使用角度
- ref定义的数据:操作数据需要.value,模板中读取数据不需要.value
- reactive定义的数据:操作与读取均不需要.value
5、toRef
- 作用:创建一个ref对象,value值指向对象中的某个属性
- 语法:const name = toRef(person, 'name')
- 应用:要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:toRefs和toRef功能一致,但可以批量创建多个ref对象
错误用法
let { name, price } = reactive({
name: '奔驰',
price: 30
})
// 直接结构出来的可以展示到视图层,但是不是响应式的
正确用法
<template>
<h1>{{name}}{{price}}</h1>
</template>
<script setup>
import { reactive,toRefs } from 'vue'
let car = reactive({
name: '奔驰',
price: 30
})
// 通过toRefs或者toRef将属性提供给外部使用,而且是响应式的
let { name, price } = toRefs( car )
let name = toRef(car, 'name')
let price = toRef(car, 'price')
// 要通过 “点value” 进行修改
</script>
6、计算属性
vue3也支持vue2的写法(不建议)
vue3中的计算属性变成了一个组合式API
import { computed } from 'vue'
setup(){
// 简写形式,没有考虑计算属性被修改的情况
let name = computed(()=>{
...
return xxx
})
// 完整写法
let name = computed({
get(){},
set(){}
})
}
6、watch监视
变成了一个组合式API,是一个函数
import { ref, reactive, watch } from 'vue'
setup(){
/**
* 监视ref定义的一个响应式数据
*/
let sum = ref(0)
let msg = ref('ok')
watch(sum, (newVal, oldVal)=>{
console.log('数据变化了')
})
// 与vue2不同,这里的watch是一种行为,而不是配置项。所以可以写多次
// 如果ref定义了一个对象类型数据,需要监听obj.value
/**
* 监视多个响应式数据可以写到数组中
*/
watch([sum, msg], (newVal, oldVal)=>{
console.log(newVal) // 结果是一个数组
})
/**
* 可以有第三个参数(配置)
*/
watch(sum, (newVal, oldVal)=>{
console.log('数据变化了')
},{immediate: true})
/**
* 监视reactive定义的一个响应式数据的全部属性
*/
let person = reactive({
name: '张三',
age: 18
})
watch(person, (newVal, oldVal) => {
console.log(newVal,oldVal)
})
/*
存在问题:
无法正常获取oldVal,变化后oldValue和newVal一样
监听的是reactive定义的对象,默认强制开启了深度监视(deep配置无效)
*/
/**
* 监视reactive定义的一个响应式数据的某个属性
*/
let person = reactive({
name: '张三',
age: 18
})
watch(()=>person.age, (newVal, oldVal) => {
...
})
// 只监视某一个属性,不能写person.age,因为只能监视ref、reactive、数组
// 必须通过函数的返回值进行监视
/**
* 监视reactive定义的一个响应式数据的某个属性
*/
let person = reactive({
name: '张三',
age: 18
})
watch([()=>person.name, ()=>person.age], (newVal, oldVal) => {
...
})
// 通过函数返回要监视的对象属性,存放在数组之中,实现监视多个属性
/**
* 特殊情况
*/
let person = reactive({
name: '张三',
age: 18,
job: {
money: 20
}
})
watch(()=>person.job,(newVal)=>{
console.log(newVal)
},{ deep:true })
// 如果只监视job,此时深度监视有效,因为监听的不再是reactive定义的对象
}
7、watchEffect监视
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
import { watchEffet } from 'vue'
watchEffect(()=>{
console.log('该回调执行了')
})
8、provide与inject
提供与注入,可以实现祖与后代组件之间的通信
父组件有一个provide选项提供数据,后代组件有一个inject选项使用数据
写法:
// 祖组件中
import { provide } from 'vue'
setup(){
let cat = reactive({name:'张三'})
// 给自己的后代组件传递数据
provide('car', car)
return {cat}
}
// 孙组件中
import { inject } from 'vue'
setup(){
// 得到传递的数据,修改后所有的数据都会发生变化
const car = inject('car')
return {car}
}
三、其他组合式API
1、shallowRef和shallowReactive
import { shallowRef, shallowReactive } from 'vue'
/**
* shallowReactive只考虑对象第一层的响应式
*/
/**
* shallowRef和Ref的区别
*/
- shallowRef不去处理对象类型的响应式,只处理基本数据类型的响应式
- 而Ref会求助Reactive实现响应式
2、readonly和shallowReadonly
readonly是一个函数,加工一个响应式数据后不可修改
shallowReadonly只会使得第一层属性不可修改
import { shallowReadonly, readonly }
setup(){
let person = reactive({
name: '张三',
age: 18,
job: {
money: {
num: 20
}
}
})
// person只读
person = readonly(person)
// person第一层只读
preson = shallowReadonly(person)
}
3、toRaw与markRaw
toRaw可以将一个由reactive生成的响应式对象转为普通对象
处理ref生成的响应式数据得到的是undefined
使用场景:用于读取响应式对象的普通对象,对普通对象的操作都不会引起页面更新
markRaw可以标记一个对象,这个对象永远都不会成为一个响应式对象
使用场景:
- 有些值不应该设为响应式,例如复杂的第三方类库
- 当渲染具有不可变数据源的大列表时候,跳过响应式转换可以提高性能
4、customRef
创建一个自定义的Ref,并对其依赖项进行跟踪和更新触发进行显示控制
实现防抖效果
<template>
<input type="text" v-model="keyWord"/>
<h3>{{keyWord}}</h3>
</template>
<script>
import { customRef } from 'vue'
export default {
name:'App',
setup(){
// 自定义ref
function myRef(val){
return customRef((track,trigger)=>{
return {
// 读取自定义ref中数据时候调用
get(){
track() // 追踪数据的改变,再次调用时返回数据
return val
},
// 修改自定义ref中数据时候调用
set(newVal){
val = newVal
trigger() // 通知vue重新解析模板,再次调用get
}
}
})
}
// 使用自定义ref
let keyWord = myRef('')
return {keyWord}
}
}
</script>
可以延迟trigger()的调用实现防抖,每次修改时清除定时器,再添加定时器
set(newVal){
clearTimeout(timer)
timer = setTimeout(()=>{
val = newVal
trigger()
},500)
}
5、响应式数据的判断API
- isRef:检查一个是是否为ref对象
- isReactive:检查一个对象是否是由reactive创建的响应式代理
- idReadonly:检查一个对象是否是由readonly创建的只读代理
- isProxy:检查对象是否是由reactive或者readonly方法创建的代理
四、路由
版本v4.x
import { useRoute, useRouter } from 'vue-router'
使用的变化
let route = useRoute()
相当于this.$route
let router = useRouter()
相当于this.$router
路由的跳转
通过router-link
进行跳转,取消了tag属性渲染成任意标签
导航守卫
1、全局
beforeEach(to, from, next){} 全局前置守卫,路由跳转前触发
beforeResolve(to, from, next){} 全局解析守卫
afterEach(to, from, next){} 全局后置守卫,路由跳转完成后触发
2、路由独享
beforeEnter(to, from, next){} 路由对象单个路由配置,路由进入前触发
3、组件
beforeRouteEnter(to, from, next){} 组件beforeCreate阶段触发
beforeRouteUpdate(to, from, next){} 当前路由改变时触发
beforeRouteLeave(to, from, next){} 导航离开该组件时触发
五、生命周期
vue2和vue3生命周期可以混合使用
文章评论