1. Vue3简介
- 2020年9月18日,Vue.js 发布3.0版本,代号: One Piece(海贼王)
- 耗时2年多、2600+次提交、30+个RFC(请求修改意见稿)、600+次PR(拉取请求)、99位贡献者
- tags地址: github.com/vuejs/vue-n…
2. Vue3带来了什么
2.1 性能的提升
- 打包大小减少41%
- 初次渲染快555,更新渲染快133%
- 内存减少54%
- ...
2.2 源码的升级
- 使用Proxy代替defineProperty实现响应式
- 重写虚拟DOm的实现和Tree-Shaking
- ...
2.3 拥抱TypeScript
- vue3可以更好的支持TypeScript
2.4 新的特性
-
Composition API (组合API)
setup配置
ref与reactive
watch与watchEffect
provide与inject
....
-
新的内置组件
Fragment
Teleport
Suspense
...
-
其他改变
新的生命周期钩子
data选项应始终被声明为一个函数
移除keyCode支持作为v-on的修饰符
...
3. 创建Vue3.0工程
1. 使用vue-cli创建
// vue -V 版本号要大于4.5.0
npm i -g @vue/cli
vue create <project-name>
cd <project-name>
npm run serve
2. 使用Vite创建
vite 新一代的前端构建工具
优势:
- 开发环境中,无需打包操作,可快速的冷启动
- 轻量快速的热重载HMR
- 真正的按需编译,不再等待整个应用编译完成。
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
4. 常用的Composition API
组合式API
4.1 setup
-
setup: Vue3.0中一个新的配置项,
值为一个函数。 -
setup 是所有
Composition API表演的舞台。 -
组件中所用到的: 数据、方法等等 ,均要配置在setup中。
-
setup函数的两种返回值: (1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用!!!!(
重点)export default { name: 'App', // 此处暂时不考虑响应式数据 setup() { //数据 let name ='糖糖糖' let age = 22 // 方法 function sayHello() { console.log(`你好啊,我叫${name},我${age}岁了`) } return { name, age, sayHello } } }(2)若返回一个渲染函数,则可以自定义渲染内容(
了解)import {h} from 'vue' export default { name: 'App', setup() { ... // 返回一个函数 我们称之为渲染函数 return () => h('h1','tanttangtang') } } }
注意: vue3 可以向下兼容vue2的写法。
- 注意点
(1)尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methods、computed.....)中
可以访问到setup中的属性、方法。 - 但在setup中
不能访问到Vue2.x配置(data、methods、computed.....) - 如果有重名,
setup优先。 (2)setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- Vue2.x配置(data、methods、computed.....)中
4.2 ref函数
ref函数的作用是定义一个响应式的数据
语法:const xxx = ref(iinitValue)
- 创建一个包含响应式数据的
引用对象(reference对象简称ref对象) - jS中操作数据:
xxx.value - 模板中读取数据: 不需要.value ,直接
<div>{{xxx}}</div>备注: - 接收的数据可以是: 基本类型、也可以是对象类型。
- 基本类型的数据: 响应式依然是靠Object.defineProperty()的get与set完成的。
- 对象类型的数据: 内部
求助了vue3.0中的一个新函数————reactive函数。
<h1>{{name}}</h1>
<h2>{{age}}</h2> // 注意不用name.value age.value
import { ref } from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref("张三")
let age = ref(18)
// 方法
function changeInfo(){
name.value = "李四" // 注意这里需要用.value
age.value = 48
}
// 返回一个对象(常用)
return {
name,
age,
changeInfo
}
}
}
用ref包装完的数据,是RefImpl的实例对象,叫引用实现的实例简称引用对象
RefImpl = > reference 引用, implement 实现
以下是ref函数 升级版本
<h1>{{name}}</h1>
<h2>{{age}}</h2> // 注意不用name.value age.value
<h2>{{job.type}}</h2>
<h2>{{job.salary}}</h2>
import {ref} from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref("张三")
let age = ref(18)
let job = ref({
type: '前端工程师',
salary: '30k'
})
// 方法
function changeInfo(){
name.value = "李四"
age.value = 48
console.log(job.value)// proxy { type: "前端工程师", salary: "30k"}
job.value.type = "UI设计师"
job.value.salary = '60k'
}
// 返回一个对象(常用)
return {
name,
age,
job,
changeInfo
}
}
}
4.3 reactive函数
作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
语法: const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象简称proxy对象)
- reactive定义的响应式数据是
深层次的。 - 内部基于Es6的Proxy实现,通过代理对象操作源对象内部数据进行操作。
import { ref, reactive } from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref("张三")
let age = ref(18)
let job = reactive({
type: '前端工程师',
salary: '30k'
})
// 方法
function changeInfo(){
name.value = "李四"
age.value = 48
console.log(job) // proxy { type: "前端工程师", salary: "30k"}
job.type = "UI设计师"
job.salary = '60k'
}
// 返回一个对象(常用)
return {
name,
age,
job,
changeInfo
}
}
}
// ------------------------------
import { reactive} from 'vue'
export default {
name: 'App',
setup() {
// 数据
let person = reactive({
name: '张三',
age: 18,
job: {
type: '前端工程师',
salary: '30k',
a: {
b: {
c: 666
}
}
},
hobby: ['抽烟','喝酒','烫头']
})
// 方法
function changeInfo(){
person.name = "李四"
person.age = 48
person.job.type = "UI设计师"
person.job.salary = '60k'
person.job.a.b.c = 999
person.hobby[0] = '学习' // vue3里面可以直接通过索引修改数组数据
}
// 返回一个对象(常用)
return {
person,
changeInfo
}
}
}
html: {{person.name}} {{person.age}} {{person.job.type} {{person.job.a.b.c}}}
4.4 vue2.0 与 vue3.0 的响应式
Vue2.x的响应式 :
实现原理:
- 对象类型:通过
Object.defineProperty()对属性的读取、修改进行拦截(数据劫持) - 数组类型: 通过重写数组的一些列方法来实现拦截。 (对数组的变更方法进行了包裹)
Object.defineProperty(data,'count',{
get(){},
set(){}
})
存在问题:
-
新增属性、删除属性,界面不会更新
-
直接通过下标修改数组,界面不会自动更新。
this.$set(this.person,'sex','女') 等同于 Vue.set(this.person,'sex','女')this.$delete(this.person,'name') 等同于 Vue.delete(this.person,'name','女')this.person.hobby.splice(0,1,'逛街')
Vue3.0的响应式:
实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射):对代理对象的属性进行操作。 Reflect是window上的方法
- MDN文档中描述的Proxy与reflect
let a = {a:1 ,b:2}
Reflect.get(obj,'a') // 读
Reflect.set(obj,'a',66666)// 改
Reflect.deleteProperty(obj,'a')// 删除
//#region
//#endregion
// !!!!!!!模拟vue3中实现响应式!!!!!!!!!
const p = new Proxy(person,{
// 有人读取p的某个属性时调用
get(target,propName) { // target源对象这里指person propName属性名
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value) {
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)
return Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName) {
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)
return Reflect.deleteProperty(target,propName)
}
})
4.5 reactive对比ref
一: 从定义数据角度对比:
- ref用来定义:
基本类型数据。 - reactive用来定义:
对象(或数组)类型数据。 - 备注: ref也可以用来定义
对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。 二: 从原理角度对比: - ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 - reactive通过使用
Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。 三: 从使用角度对比: - ref定义的数据: 操作数据
需要.value ,读取数据时模板中直接读取不需要.value 。 - reactive定义的数据: 操作数据与读取数据:
均不需要.value 。
//写成一个对象
let data = reactive({
person: {},
user: {}
})
return {
data
}
4.6 setup的两个注意点
-
setup执行的时机 在
beforeCreate之前执行一次,this是undefined。 -
setup的参数
- props: 值为对象,包含: 组件外部传递过来,且组件内部声明接收了的属性。
- context: 上下文对象
attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当
于this.$attrs
slots: 收到的插槽内容,相当于this.$slotsemit: 分发自定义事件的函数,相当于this.$emitexport default { name: 'Demo', beforeCreate() { console.log('-------default-----') }, props: ['msg','school'], // vue2和vue3都支持这个配置,告诉vue 我要接收msg ,school emits: ['hello'], //vue3里面必须要写!!!,告诉它我知道了我要触发hello事件 setup(props,context) { // this是unfined,所以setup里面是不会出现this console.log('--------setup----------',this,props,context) console.log(context.attrs) // 相当于vue2中的$attrs console.log(context.slots) //插槽 console.log(context.emit) //触发自定义事件 // 数据 let person = reactive({ name: '张三', age: 18 }) // 方法 function test() { context.emit('hello',6666) // hello是事件,6666是值 } // 返回一个对象(常用) return { person, test } } }
setup能接收两个参数,第一个是props , 第二个是context
给组件绑定的事件是自定义事件, 如果非要绑定原生事件比如click需要加.native 。 @click.native='add'
<template slot="absc"></template> 第二种写法 <template v-slot:absc></template>
(vue3推荐这种写法)
4.7 计算属性与监视
-
computed 函数
与Vue2.x中computed配置功能一致
写法一: import { reactive, computed } from 'vue' // 因为计算属性变成了可组合式的api可以必须要引入 setup() { ... // 数据 let person = reactive({ firstName: '张', lastName: '三' }) //计算属性——————简写(没有考虑计算属性被修改的情况) let fullName = computed(() => { return person.firstName + '-' + person.lastName }) // 计算属性 —————— 完整(考虑读和写) let fullName = computed({ // 传入一个对象 get() { return person.firstName + '-' + person.lastName }, set(value) { const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) return { person, fullName } }写法二: import { reactive, computed } from 'vue' // 因为计算属性变成了可组合式的api可以必须要引入 setup() { ... // 数据 let person = reactive({ firstName: '张', lastName: '三' }) //计算属性——————简写(没有考虑计算属性被修改的情况) person.fullName = computed(() => { return person.firstName + '-' + person.lastName }) // 计算属性 —————— 完整(考虑读和写) person.fullName = computed({ // 传入一个对象 get() { return person.firstName + '-' + person.lastName }, set(value) { const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) return { person } } -
watch 函数
与vue2.x中watch配置功能一致
两个小坑:
-
监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)。
-
监视reactive定义的响应式数据中某个属性时: deep配置有效。
-
写法:
import { ref,watch } from 'vue'
// 数据
let sum = ref(0)
let msg = ref('你好啊')
情况一: 监视ref定义的一个响应式数据
watch(sum,(newValue,oldValue) => {
console.log(`sum变化了`,newValue,oldValue)
},{immediate: true})
情况二: 监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue) => {
console.log(`sum或msg变化了`,newValue,oldValue)
})
import {reactive} from 'vue'
情况三: 监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获取oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
watch(person,(newValue,oldValue) => {
console.log(`person变化了`,newValue,oldValue)
},{ immediate: true,deep: false }) // 此处的deep配置无效
情况四: 监视reactive定义的响应式数据中的某个属性
watch(() => person.job,(newValue,oldValue) => { // 写成一个函数,返回你想监听属性
console.log(`person的job变化了`,newValue,oldValue)
},{immediate: true,deep: true})
情况五: 监视reactive定义的响应式数据中的某些属性
watch([() => person.job,()=>person.name],(newValue,oldValue) => {
console.log(`person的job或name变化了`,newValue,oldValue)
},{immediate: true,deep: true})
// 特殊情况
person.job = {
j1: {
salary: 20
}
}
watch(() => person.job,(newValue,oldValue) => { // 写成一个函数,返回你想监听属性
console.log(`person的job变化了`,newValue,oldValue)
},{immediate: true,deep: true}) // 此处由于监视的是reactvie所定义的对象中的某个属性,所以deep配置有效。
vue2.x 写法
watch:{
sum(newValue,oldValue){
console.log('sum的值变化了',newValue,oldValue)
},
// 完整写法
sum : {
immediate: true,
deep: true,
handler(newValue,oldValue) {
console.log('sum的值变化了',newValue,oldValue)
}
}
}
watchEffect函数
- watch的套路是: 既要指明监视的属性,也要指明监视的回调。
- watchEffect的套路是: 不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
import { ref,reactive,watchEffect } from 'vue'
export default {
name: 'Demo',
setup(){
// 数据
let sum = ref(0)
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
//
watchEffect(()=>{ // 会看{}里面用到了谁,它就监视谁,特别智能. 我不说我监视谁,你用谁我就监视谁
const x1 = sum.value
const v2 = person.age
const x2 = person.job.j1.salary
console.log(`watchEffect配置的回调执行了`)
})
return {
sum,
person
}
}
}
4.8 vue3 生命周期
把vue2里面的beforeDestroy 换成 beforeUnmount ,destroyed 换成 unmounted
//vue3.0 生命周期
export default {
name: 'App',
components: {Demo},
setup() {
let isShowDemo = ref(true)
return {
isShowDemo
}
},
// 通过配置项的形式使用生命周期钩子
beforeCreate() {
console.log('---beforeCreate----')
},
created() {
console.log('---created----')
},
beforeMount() {
console.log('---beforeMount----')
},
mounted() {
console.log('---mounted----')
},
beforeUpdate() {
console.log('---beforeUpdate----')
},
updated() {
console.log('---updated----')
},
beforeUnmount() {
console.log('---beforeUnmount----')
},
unmounted() {
console.log('---unmounted----')
}
}
因为Vue3.0 也提供了Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate ==> setup()created ====> setup()beforeMount ===> onBeforeMountmounted ====> onMountedbeforeUpdate ===> onBeforeUpdateupdated ===> onUpdatedbeforeUnmount ===> onBeforeUnmountunmounted ===> onUnmounted以上改为下面的:
//通过组合式API的形式去使用生命周期钩子
import {ref ,onBeforeMount,onMounted,onBeforeUpdate, onUpdated,onBeforeUnmount,onUnmounted } from 'vue'
export default {
name: 'App',
components: {Demo},
setup() {
// 数据
let isShowDemo = ref(true)
//通过组合式API的形式去使用生命周期钩子
// beforeCreate created 相当于setup () 所以不能放在这里面
onBeforeMount(() => {
console.log('-----onBeforeMount-----')
})
onMounted(() => {
console.log('-----onMounted-----')
})
onBeforeUpdate(() => {
console.log('-----onBeforeUpdate-----')
})
onUpdated(() => {
console.log('-----onUpdated-----')
})
onBeforeUnmount(() => {
console.log('-----onBeforeUnmount-----')
})
onUnmounted(() => {
console.log('-----onUnmounted-----')
})
//返回一个对象(常用)
return {
isShowDemo
}
},
}
4.9 自定义hook函数
什么是hook ? 本质是一个函数,把setup函数中使用的Composition API进行了封装。 类似于vue2.x中的mixin 自定义hook的优势: 复用代码,让setup中的逻辑更清楚易懂。
import {ref ,reactive,onMounted,onBeforeUnmount } from 'vue'
export defalut {
name: 'Demo',
setup() {
// 数据
let sum = ref(0)
let point = reactive({
x: 0,
y: 0
})
// 方法
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
}
// 生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
// 返回一个对象(常用)
return {
sum,
point
}
}
}
下面用hooks
定义一个hooks文件夹 , 在里面usePoint.js
hooks文件夹下的文件一般命名以use开头
import {reactive,onMounted,onBeforeUnmount } from 'vue'
function savePoint() {
//实现鼠标打点相关的数据
let point = reactive({
x: 0,
y: 0
})
// 实现鼠标打点相关的方法
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
}
// 实现鼠标打点相关的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
// 暴露出去
export default savePoint
或者
import {reactive,onMounted,onBeforeUnmount } from 'vue'
export default function() {
//实现鼠标打点相关的数据
let point = reactive({
x: 0,
y: 0
})
// 实现鼠标打点相关的方法
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
}
// 实现鼠标打点相关的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
在其他页面 引入
import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export defalut {
name: 'Demo',
setup() {
// 数据
let sum = ref(0)
let point = usePoint()
// 返回一个对象(常用)
return {
sum,
point
}
}
}
总之: ref , reactive ,计算属性,监听属性,生命周期等等 ,只要在setup里面写的都是Composition API 组合式API。hooks就是把这些组合式API进行了封装
4.10 toRef 与 toRefs
toRef : 作用: 创建一个ref对象,其value值指向另一个对象中的某个属性。
语法: const name = toRef(person,'name') (想要person对象里面的name属性)
应用: 要将响应式对象中的某个属性单独提供弄个给外部使用时。
扩展:toRefs 与 toRef功能一致,但可以批量创建多个ref对象,语法: toRefs(person)
返回值是RefImpl实例对象
总结:toRef只能给你处理一个对象中的某个属性,toRefs能批量处理一个对象中的所有属性
// vue3里面实现对象响应式类型用的proxy
let p = new Proxy(person,{
set(target,propName,value){
console.log(`${propName}被修改了,我要去更新界面了`)
Reflect.set(target,propName,value)
}
})
解决方案一:
import {reactive,toRef} from 'vue'
// toRef帮你把一个不是ref的东西变成ref
export defalut {
name:'Demo',
setup() {
/// 数据
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
//const name1 = toReft(person,'name') // 想要person对象里面的name属性,并把它变成ref
// name1 分两步: 第一步转成RefImpl{...value} 间接找的就是person.name ,通过getter引用了一下,改的就是person里面的name。
//返回一个对象(常用)
return {
name: toRef(person,'name') // 如果这里写ref(person.name) 这里改的不是person里面的name ,改的是ref(person.name)的值,数据就分家了。。。
age: toRef(person,'age') // 这里不能写ref(person.age) // 不然原数据都不会变。
salary: toRef(person.job.j1,'salary') // 第一个参数传一个对象 // 这里也不能写ref(person.job.j1.salary)原因和上面一样
}
}
}
解决方案二:
页面: 姓名: {{name }} {{age}} {{job.j1.salary}}
import {reactive,toRefs} from 'vue'
// toRef帮你把一个不是ref的东西变成ref
export defalut {
name:'Demo',
setup() {
/// 数据
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
// toRef只能给你处理一个对象中的某个属性,toRefs能批量处理一个对象中的所有属性
//const x = toRefs(person)
//console.log(x) // x是新的对象,里面的属性都是一个ref响应式对象
//返回一个对象(常用)
return {
person,
...toRefs(person)
}
}
}
// toRefs(person)返回值是一个对象,所以要用...toRefs(person)
//总结修改数据的时候,原数据也会跟着变。
5. 其他Composition API
5.1 shallowReactive 与 shallowRef
-
shallowReactive: 只处理对象最外层属性的响应式(浅响应式) -
shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理。 什么时候使用?
如果有一个对象数据 ,结构比较深,但变化时只是外层属性变化===shallowReactive
如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===shallowRef
页面: 姓名: {{name }} {{age}} {{job.j1.salary}}
import { reactive,shallowReactive,shallowRef } from 'vue'
// toRef帮你把一个不是ref的东西变成ref
export defalut {
name:'Demo',
setup() {
/// 数据
//let person = shallowReactive({
// shallowReactive 浅层次的响应式,只考虑对象类型中的第一层的响应式
// reactive会遍历所有层次的对象变成响应式
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
// let x = ref(0)
// let x= shallowRe(0)
let x = shallowRef({
y: 0
})
let x = ref({
y: 0
})
// shallowRef不去处理对象类型的响应式
// ref可以处理基础类型的响应式,也可以处理对象类型的响应式
//返回一个对象(常用)
return {
person,
...toRefs(person)
}
}
}
5.2 readonly 与 shallowReadonly
readonly :让一个响应式数据变为只读的(深只读)。
shallowReadonly: 让一个响应式数据变为只读的(浅只读)。
应用场景:: 不希望数据被修改时。
import {ref,reactive,toRefs,readonly,shallowReadonly} from 'vue'
export default {
name: 'Demo',
setup(){
// 数据
let sum = ref(0)
let person = reactive({
name: '糖糖糖',
age: 22,
job: {
j1: {
salary: 20
}
}
})
// person = readonly(person)
person = shallowReadonly(person) // person里面的薪资可以改,其他都不能改
// 返回一个对象(常用)
return {
sum ,
...toRefs(person)
}
}
}
5.3 toRaw 与 markRaw
toRaw: 作用: 将一个由reactive生成的响应式对象转为普通对象。
使用场景: 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
markRaw: 作用: 标记一个对象,使其永远不会再称为响应式对象。
应用场景:
1、有些值不应被设置为响应式的,例如复杂的第三方类库等。
2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
把普通数据变成响应式数据,我们用ref ,reactive
把响应式数据变成普通数据,我们用toRaw , markRaw
import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
export default {
name: 'Demo',
setup(){
// 数据
let sum = ref(0)
let person = reactive({
name: '糖糖糖',
age: 22,
job: {
j1: {
salary: 20
}
},
//car:{} 第一种写法
})
// 方法
function showRawPerson() {
let p = toRaw(person)
console.log(p)
p.age ++ // 页面不会更新
const sum = toRaw(sum)
console.log(sum)
}
function addCar() {
let car = {name: '奔驰',price: '40W'}
// person.car = car // 给响应式对象里添加数据,也是响应式的
person.car = markRaw(car) //使用markRaw car就不是响应式数据
}
// person = readonly(person)
person = shallowReadonly(person) // person里面的薪资可以改,其他都不能改
// 返回一个对象(常用)
return {
sum ,
person,
// ...toRefs(person),
showRawPerson,
addCar
}
}
}
5.4 customRef
作用 : 创建一个自定义的ref ,并对其依赖项跟踪和更新触发进行显示控制。
实现防抖效果:
<template>
<input type="text" v-model="keyword">
<h3>{{keyword}}</h3>
</template>
<script>
import {ref, customRef} from 'vue'
export default {
name: 'Demo',
setup() {
// let keyword = ref('hello') //使用Vue准备好/提供的内置ref
// 使用自定义一个ref叫myRef
// 所谓的自定义ref就是一个函数 ,也不是我们完全自己写,我们要借助一个api 叫customRef
function myRef(value,delay) {
let timer
// 通过customRef去实现自定义
return customRef((track,trigger) => {
// vue官网提供这样写 ,既然是自定义那一定要写一个函数。而且也必须返回return出去一个对象。
return { // 语法要求 ,get ,set 也是内置的
get() { // 读
//return 100 // 返回值100
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
track()// 通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的。)
return value // 第三步: return出去。
},
set(newValue) { // 改
console.log(`有人把myRef这个容器中的数据改了,我把${newValue}给他了`)
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue // 第一步 改数据
trigger() // 第二步: 通知vue去重新解析模板
},delay)
}
}
}) // 一定要返回return出去,不然就没意义
}
let keyword = myRef('hello',500)
//使用Vue准备好/提供的内置ref,500 是延迟时间。 目的是让数据延迟一会再执行
return {
keyword
}
}
}
</script>
5.5 provide 与 inject
provide 提供,inject 注入
作用: 实现祖孙组件间通信 (祖孙组件也叫跨级组件也叫祖与后代组件)
套路: 父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这些数据 具体写法:
1、祖组件中:
import { provide, reactive , toRefs } from 'vue'
setup() {
...
let car = reactive({name: '奔驰',price: '40万'})
provide('car',car) // 给自己的后代组件提供数据
return {...toRefs(car)}
}
2、孙组件中:
import { inject } from 'vue'
setup(props,context) {
...
const car = inject('car') // 使用这些数据,还是一个响应式的数据呢
return { car }
...
}
5.6 响应式数据的判断
isRef: 检查一个值是否为一个ref对象
isReactive: 检查一个对象是否由reactive创建的响应式代理
isReadonly: 检查一个对象是否由readonly创建的只读代理
isProxy: 检查一个对象是否由reactive 或者 readonly 方法创建的代理
6. Composition API 的优势
-
Options API(配置式的API)存在的问题 使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。 -
Composition API(组合式的API)的优势 我们可以更加优雅的组织我们的代码,函数,让相关功能的代码更加有序的组织在一起。
7. 新的组件
7.1 Fragment
-
在Vue2中: 组件必须有一个根标签
-
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个
Fragment虚拟元素中 -
好处: 减少标签层级, 减小内存占用
7.2 Teleport
什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
7.3 Suspense
Suspense 官方说目前处于实验阶段
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
异步引入组件
import {defineAsyncComponent} from 'vue' // 定义一个异步组件 静态引入
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
// 异步引入或 动态引入,要求必须传入一个函数并且有返回值
// 使用Suspense包裹组件,并配置好default 与 fallback
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>
8. 其他
8.1 全局API的转移
- Vue 2.x 有许多全局 API 和配置。例如:注册全局组件、注册全局指令等。
/注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
- Vue3.0中对这些API做出了调整:
将全局的API,即:Vue.xxx调整到应用实例(app)上
| 2.x 全局 API(Vue) | 3.x 实例 API (app) |
|---|---|
| Vue.config.xxxx | app.config.xxxx |
| Vue.config.productionTip | 移除 |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
| Vue.prototype | app.config.globalProperties |
8.2 其他改变
(1)、 data选项应始终被声明为一个函数。
(2)、过度类名的更改:
Vue2.x写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3.x写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
(3)、 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes(兼容性差所以移除了)
(4)、 移除v-on.native修饰符
(a) 父组件中绑定事件
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
绑定了close,click (b) 子组件中声明自定义事件
<script>
export default {
emits: ['close'] // 用emits 来说明close是自定义事件
}
</script>
(5)、 移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。 .....
还有其他的,具体看官方文档。