一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
一、Vue3 快速上手
1、vue3 带来了什么
1.1、性能的提升
- 打包大小减少41%
- 初次渲染快55%,更新渲染快133%
- 内存减少54%
2.2、源码的升级
- 受用Proxy代替defineProperty实现响应式
- 重写虚拟DOM的实现和Tree-Shaking(去除未使用的JS代码)
2.3更好的支持TypeScript
2.4新的特性
Composition API (组合API)
- setup 配置
- ref 和 reactive
- watch 和 watchEffect
- provide 和 inject
新的内置组件
- Fragment
- Teleport
- Suspense
其他改变
- 新的声明周期钩子
- data选项应始终被声明为一个函数
- 移除keyCode 支持作为v-on 的修饰符
2、使用vite 创建
- 什么是vite --- 新一代的前端构建工具
- 优势如下:
- 开发环境钟,无需打包,可以快速的冷启动(动态的根据路由来加载对应模块)
- 轻量快速的热重载(HMR)
- 真正的按需编译,不再等待整个应用编译完成
使用vite 创建项目
npm init vite-app vue3_test_vite
cd .\vue3_test_vite\
npm install
npm run dev
3、使用vue-cli 创建
vue create 项目名称
主页挂载根vue2的区别
// 引入的不再是Vue构造函数,引入的是createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例对象-app (类似于之前Vue2中的vm,但app比vn更轻
const app = createApp(App)
// 挂载
app.mount('#app')
// Vue2 中挂载
// const vm = new Vue({
// render:h=>h(App)
// })
// vm.$mount("#app")
Vue 3 组件中不需要根标签
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
二、常用的 Composition API
1、setup函数
-
理解:Vue3 里的新配置,作为一个函数
-
组件中所用到的:数据、方法等等,均要配置在setup中
-
setup 函数的两种返回值:
1、一个返回一个对象,对象中的属性、方法,在模板中可以直接使用
2、返回一个渲染函数,则可以自定义渲染内容
-
不考虑响应式的初次写法
<template>
<h1>{{name}}</h1>
<h2>{{age}}</h2>
<button @click="sayName">点击查看名字</button>
</template>
<script>
import {h} from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = 'zs'
let age = 18
// 方法
function sayName(){
alert(`这个人的名字${name},年龄是${age}`)
}
// 第一种返回一个对象(常用)
return{
name,
age,
sayName
}
// 第二种放回一个(渲染函数) 在使用 h 前需要从vue中导入
return ()=>h('h1',name)
}
}
</script>
==注意点:==
- 尽量不要与Vue2混用
- setup不能是一个async 函数,因为返回值不再是return的对象,而是promise,模板看不到return 对象中的属性,(但是后期可以搭配异步组件使用)
2、ref 函数
作用:定义一个响应式的数据
在vue3中若是想实现响应式的数据变化,需要从vue中引入一个ref函数
并且在取值的时候需要在后面加上.value,但是在模板上不用加,因为vue帮我们自动加了
<template>
<h1>{{name}}</h1>
<h2>{{age}}</h2>
<button @click="changeName">点我改变名字</button>
</template>
import {ref} from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref('zs')
let age = ref(18)
// 方法
function changeName(){
name.value = '李四',
age.value = 20
}
// 第一种返回一个对象(常用)
return{
name,
age,
changeName
}
我们打印一下ref获得的属性:ref("name") 获得一个RefImpl ==引用实例==
可以发现实际上vue3实现响应式还是通过definePropety的set,get方法实现的
对于对象的数据 实现的响应式
setup() {
// 数据
let name = ref('zs')
let age = ref(18)
let obj = ref({
type:"aaa",
salay:19999
})
console.log(obj.value);
// 打印出的结果为
//Proxy {type: "aaa", salay: 19999}
==对于对象==它的响应式是ref获得实例后,再用proxy 来代理属性的变化,实际上还是借助了Vue3里的 reactive 函数去实现
3、reactive 函数
作用: 定义一个对象类型的响应式数据
语法: const 代理对象 = reactive (源对象) 接受一个(对象 / 数组) 返回一个代理对象*(Proxy的实例对象)*
import {ref ,reactive} from 'vue'
let name = ref('zs')
let age = ref(18)
let obj = reactive({
type:"aaa",
salay:19999
})
// 取值的时候就不用在后面加.value 了
// 方法
function changeName(){
name.value = '李四',
age.value = 20,
obj.type = "五六"
}
4、Vue3.x 的响应式
-
实现原理:
-
用过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写,属性的添加、属性的删除等
-
通过Reflect (反射) :对被代理对象的属性进行操作
Proxy(代理)
let Person = { name:'张三', age:19 } const p = new Proxy(Person,{ //target 代表源数据Person 的对象 // propValue 代表要操作的对应属性 // newValue 代表要操作的新的值 get(target,propValue){ // 读取属性时调用 console.log(`读取Person的属性返回对应值`); return target[propValue] }, set(target,propValue,newValue){ // 修改跟增加时掉用 console.log("Person属性发生了修改跟增加,去页面做对应操作"); target[propValue] = newValue }, deleteProperty(target,propValue){ // 删除时调用 console.log("Person属性要被删除"); return delete target[propValue] } })
Reflect (反射)
window.Reflect 是ES6上新增的方法,上面包含了许多Object上的方法
// 使用object.defineProperty() 一旦报错就容易使js运行不下去 //使用Reflect 使用对应的方法,它会将defineProperty 的运行结果以布尔值返回,不会影响后面代码执行 // 相对使用Reflect 反射可以减少try catch 的使用 const p = new Proxy(Person,{ //target 代表源数据Person 的对象 // propValue 代表要操作的对应属性 // newValue 代表要操作的新的值 get(target,propValue){ // 读取属性时调用 console.log(`读取Person的属性返回对应值`); return Reflect.get(target,propName) }, set(target,propValue,newValue){ // 修改跟增加时掉用 console.log("Person属性发生了修改跟增加,去页面做对应操作"); Reflect.set(target,propName,newValue) }, deleteProperty(target,propValue){ // 删除时调用 console.log("Person属性要被删除"); return Reflect.deleteProperty(target,propName) } })
-
5、reactive 和 ref 的对比
- 从定义数据的角度
- ref 用来定义:基本类型数据
- reactive 用来定义:对象(或数组)类型数据
- 其中:ref 也可以用来定义 对象(或数组)类型数据,它内部会自动通过 ==reactive== 转为代理对象
- 从原理角度:
- ref通过 Object.defineProperty() 的 get 与 set 来实现响应式 (数据劫持)
- reactive 通过使用 Proxy 来实现响应式(数据劫持),并通过 Reflect 操作源对象内部的数据
- 从使用角度对比:
- ref 定义的数据: 操作数据都需要 .value
- reactive 定义的数据 : 操作数据与读取数据 , 均不需要 .value
6、setup的两个注意点
- setup 执行的时机
- 在 beforeCreate 之前执行一次,this 是 undefined
- setup 的参数
- props:值为对象,包含 : 组件外部传递过来,且组件内部声明接收了的属性。
- context :上下文对象
- attrs: 值为对象,包含:组件外部传过来,但没有在 props 配置声明的属性,相当于 ==this.$attrs==
- slots: 收到的插槽的内容,相当于 ==this.$slots.==
- emit : 分发自定义事件的函数, 相当于 ==this.$emit==
7、computed 函数与 watch 函数
1、computed 函数
-
与Vue2.x 中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 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] } }) }
2、watch 函数
- 与Vue2.x 中watch 配置功能一致
- 两个小 坑:
- 监视 reactive 定义的响应式数据时(对象 / 数组),oldValue 无法获取正确的值,强制开启了深度监视 (deep配置有无都一样)都可以获取到深处值
- 监视 reactive 定义的数据中的某个对象 / 数组时,deep 配置有效
- 监视 reactive 定义的数据中的具体属性时,oldVaule 能获取正确的值
import { watch } from 'vue'
// 情况一: 监视 ref 定义的响应式数据
// 第一个参数,需要监视的对象,
// 第二个参数,回调的函数里面有新值,跟旧值,两个值
// 第三个参数,watch的配置项。可以配置immediate(页面加载时第一次是否执行),deep(深层次属性是否监听)
watch( sum , (newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
// oldVaulue 可以正确获取
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
// oldValue 可以正常获取
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况 监视对象类型的需要开启deep,此时的obj 没有被Proxy代理所以需要开启
watch(()=>person.obj,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
7.1 watchEffect 函数
- watch的套路是:既要指明监视的属性,也要指明监视的回调。
- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 刚加载初期就会回调一次
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
//页面加载初期会回调一次函数
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
8、Vue 3 生命周期
-
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
-
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
-
这些可以写进setup函数里(使用前需引用)
setup(){ `beforeCreate`===>`setup()` `created`=======>`setup()` `beforeMount` ===>`onBeforeMount` `mounted`=======>`onMounted` `beforeUpdate`===>`onBeforeUpdate` `updated` =======>`onUpdated` `beforeUnmount` ==>`onBeforeUnmount` `unmounted` =====>`onUnmounted` }
9、自定义hook函数
- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂
一个简单的获取鼠标坐标hook
// 在hooks 问价夹下建立一个 usePoint.js 将鼠标点击获取到的值返回出去,给外界调这个方法时就会得到鼠标当前的值
import {reactive,onMounted,onUnmounted} from 'vue'
export default function usePoint(){
let point = reactive({
x:0,
y:0
})
function getPoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY);
}
onMounted(()=>{
window.addEventListener('click',getPoint)
})
onUnmounted(()=>{
window.removeEventListener('click',getPoint)
})
return point
}
// 外界直接引入就可以获得里面返回的值了
<template>
<h1>我是test</h1>
<h1>鼠标当前坐标:x:{{ point.x }}y:{{ point.y }}</h1>
</template>
<script>
import usePoint from "../hooks/usePoint";
export default {
setup() {
let point = usePoint(); // 在这边直接使用函数即可获得其返回值
return {
point,
};
},
};
</script>
10、toRef 和 toRefs
- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 使return 出去的属性可以更简洁
- 语法:
const name = toRef(person,'name')
把person 对象里的name引用回原数据里的name - 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
// const name1 = person.name
// console.log('%%%',name1)
// const name2 = toRef(person,'name')
// console.log('####',name2)
const x = toRefs(person)
console.log('******',x)
//返回一个对象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
三、其它 Composition API
1、shallowReactive 和 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?(性能有所优化)
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换(x={y:newValue}) ===> shallowRef。
2.readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。(只有第一层不能改)
- 应用场景: 不希望数据被修改时。
3.toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
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
//自定义一个myRef
function myRef(value,delay){
let timer
//通过customRef去实现自定义
return customRef((track,trigger)=>{
return{
get(){
track() //告诉Vue这个value值是需要被“追踪”的
return value
},
set(newValue){
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //告诉Vue去更新界面
},delay)
}
}
})
}
let keyword = myRef('hello',500) //使用程序员自定义的ref
return {
keyword
}
}
}
</script>
5.provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
6.响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
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>
3.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>
-
六、其他
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.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
2.其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
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; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
-
......
组件上的 v-model
当需要维护组件内外数据的同步时,可以在组件上使用 v-model
指令。
父组件传值:
<!-- 父组件传值 -->
<my-counter v-model:number="count"></my-counter>
子组件在 emits
节点声明自定义事件,格式为 update:xxx
,调用 $emit
触发自定义事件:
export default {
props: ['number'],
emits: ['update:number'],
methods: {
add() {
this.$emit('update:number', this.number++)
},
},
}
注意,在 vue3
中 props
属性同样是只读的,上面 this.number++
并没有修改 number
的值。
其实通过 v-bind
传值和监听自定义事件的方式能实现和 v-model
相同的效果。
EventBus
借助于第三方的包 mitt
来创建 eventBus
对象,从而实现兄弟组件之间的数据共享。
安装 mitt
依赖包:
npm install mitt@2.1.0
创建公共 eventBus
模块:
import mitt from 'mitt'
// 创建 EventBus 实例对象
const bus = mitt()
export default bus
数据接收方调用 bus.on()
监听自定义事件:
import bus from './eventBus.js'
export default {
data() {
return { count: 0 }
},
created() {
bus.on('countChange', (count) => {
this.count = count
})
},
}
数据接发送方调用 bus.emit()
触发事件:
import bus from './eventBus.js'
export default {
data() {
return { cout: 0 }
},
methods: {
addCount() {
this.count++
bus.emit('countChange', this.count)
},
},
}
vue 3.x 全局配置 axios
实际项目开发中,几乎每个组件中都会使用 axios
发起数据请求。此时会遇到如下两个问题:
- 每个组件中都需要导入
axios
(代码臃肿) - 每次发请求都需要填写完整的请求路径(不利于后期的维护)
在 main.js
文件中进行配置:
// 配置请求根路径
axios.defaults.baseURL = 'http://api.com'
// 将 axios 挂载为 app 全局自定义属性
// 每个组件可通过 this.$http 访问到 axios
app.config.globalProperties.$http = axios
组件调用:
this.$http.get('/users')