Vue3快速上手
一、认识Vue3
1、Vue3特性
- Vue3支持Vue2大多数特性
- 更好的拥抱TypeScript
- 打包体积小,渲染速度快
- 使用Proxy代替defineProperty实现响应式数据
- 重写虚拟Dom的Tress-Shaking
- 组合式API
2、创建Vue3项目
(1)使用Vue-cli创建
// 安装vue
npm install -g @vue/cli
// 查看vue版本
vue -V
// 创建项目
vue create <项目名称>
(2)使用Vite创建
npm init vite-app <项目名称>
cd <项目名称>
npm install
npm run dev
二、Composition API
1、setup和ref的使用
(1)setup
- 新的option,所有的组合API函数都在此使用,只在初始化时执行一次
- 函数如果返回对象,对象中的属性或方法,模板中可以直接使用
(2)ref
作用:定义一个响应式数据
语法:const xxx = ref(初始值)
创建一个包含响应式数据的引用(reference)对象
js中操作数据 xxx.value
模板中操作数据:xxx.value
模板中操作数据,不需要.value
一般用来定义一个基本类型的响应式数据
// 使用
<h3>{{count}}</h3>
// 定义
setup(){
let count = ref(0)
function updateCount(){
count.value++
}
// 必须返回出去
return{
count,
updateCount
}
}
2、reactive使用
- 作用:定义多个数据的响应式
- const proxy = reactive(obj):接受一个对象返回值是该对象的代理器对象
- 响应式转换是“深层的”,会影响对象内部所有嵌套的属性
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
// 使用
<h2>reactive的基本使用</h2>
<h3>姓名:{{user.name}}</h3>
<h3>年龄:{{user.age}}</h3>
<h3>孩子{{user.son}}</h3>
<button @click="updateUser">点我更新数据</button>
// 定义
setup(){
const obj ={
name:'张三',
age:18,
son:{
name:'张小明',
age:5,
hobby:['篮球','游戏','足球','画画']
}
}
// 定义对象
// 把数据变成响应式
// 返回的是一个Proxy的代理对象,被代理的目标对象就是obj对象
// user是代理对象,obj是目标对象
const user = reactive(obj)
console.log(user);
// 函数
const updateUser = ()=>{
user.name += '!!'
user.age += 2
user.son.name += '&&'
user.son.age ++
user.son.hobby[1] = '开车'
}
return{
user,
updateUser
}
}
如果操作代理对象,目标对象中的数据也会随之发生变化,同时如果想要在操作数的时候界面也发生变化,那么也是操作代理对象
user.gender = '男'
delete user.age
(3)Vue3的响应式原理
核心:
- 通过Proxy(代理):拦截对data任意属性的任意操作、包括属性值的读写、属性的添加,属性的删除等
- 通过Reflect(反射):动态对被代理对象的相应属性进行特定的操作
const user = {
name: "张三",
age: 28,
son: {
name: "张四",
age: 16,
},
};
const proxyUser = new Proxy(user, {
// 读取目标对象上的某个属性
get(target, prop) {
return Reflect.get(target, prop);
},
// 修改目标对象上的某个属性
set(target, prop, val) {
return Reflect.set(target, prop, val);
},
// 删除目标对象上的某个属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
},
});
// 通过代理对象获取目标对象上的某个属性值
proxyUser.name = "李四";
console.log(proxyUser.name);
// 通过代理对象修改目标对象上的某个属性值
proxyUser.gender = "男";
console.log(user);
// 通过代理对象删除目标对象上的某个属性值
delete proxyUser.age;
console.log(user);
// 更新目标对象中的某个属性对象中的属性值
proxyUser.son.name = "张麻子";
console.log(user);
(4)Setup的细节问题
- setup的执行时机
1、在beforeCreate之前就执行了(一次),此时组件还没有创建。
2、this是undefined,不能通过this访问data/computed/methods/props
3、其实所有的conposition API相关回调函数都不可以
beforeCreate(){
console.log('beforeCreate执行了');
},
setup(){
console.log('setup执行了',this); // undefined
const msg = ref('what are you doing?')
return{
msg
}
}
打印顺序为:setup执行了 undefined
beforeCreate执行了
- setup的返回值:
1、一般都是返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性和方法。
2、返回对象中的属性和方法会与data函数和methods中的方法合并成为组件对象的属性和方法。
3、一般不能混合使用二者。
4、setup不能是一个async函数:因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性数据。 - setup的参数:
1、setup(props,context) / setup(prop,{attrs,slots,emit})。
2、props:是一个对象,里面有父给子传递的数据,且在子组件中用props接收到的所有的属性。
3、attras:包含没有在props配置中声明的属性的对象,相当于this.attrs。4、slots:包含所有传入的插槽内容的对象,相当于this.attrs。 4、slots:包含所有传入的插槽内容的对象,相当于this.attrs。4、slots:包含所有传入的插槽内容的对象,相当于this.slots
5、emit:用来分发自定义事件的函数,相当与this.$emit
// 父组件
<Child :msg="msg" :count="count" msg2="哈哈" @jia = "add"/>
setup(){
console.log('setup执行了',this); // undefined
// 定义一个ref数据
const msg = ref('what are you doing?')
const count = ref(10)
function add(num:number){
count.value += num
}
return{
msg,
count,
add
}
}
// 子组件
<button @click="emitAdd">分发事件</button>
name: "Child",
props: ["msg", "count"],
setup(props, context) {
// props:是一个对象,里面有父给子传递的数据,且在子组件中用props接收到的所有的属性。
console.log(props.msg);
// attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs。
console.log(context.attrs);
// emit:用来分发自定义事件的函数,相当与this.$emit
console.log(context.emit);
function emitAdd() {
context.emit('jia',2)
}
return {
emitAdd,
};
},
(5)ref和reactive的细节问题
- ref用来处理基本数据类型,reactive用来处理对象(递归深度响应式)
- 如果ref是数组/对象,内部会自动转换为reactive的代理对象
- ref内部:通过给value属性添加setter和getter方法来实现对数据的劫持
- reactive:通过使用proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
- ref的数据操作:在js中要.value,在模板中不需要(内部解析模板时会自动添加.value)
const m1 = ref('app')
const m2 = reactive({
name:'佐助',
car:{
name:'奔驰'
}
})
const m3 = ref({
name:'上官婉儿',
husband:{
name:'上官瑞谦'
}
})
function updateData(){
m1.value += '!!'
m2.name += '@@'
m3.value.name += '$$'
m3.value.husband.name += '%%'
}
return{
m1,
m2,
m3,
updateData
}
}
(6)计算属性与监视属性
- computed函数:
1、与computed配置功能一致
2、只有getter
3、有setter和getter - watch函数:
1、与watch配置功能一致
2、监视指定的一个或多个响应式数据,一旦数据变化,就会自动执行监视回调
3、默认初始时不执行回调,但可以通过配置immediate为true,指定第一次监听
4、通过配置deep为true实现深度监视 - watchEffect函数:
1、不用直接指定要监视的数据,回调函数中使用的哪些响应式数据就监视哪些响应式数据
2、默认初始时就会执行第一次,从而可以收集需要监视的数据
3、监视数据发生变化时回调
<template>
<h1>watch属性和computed属性</h1>
<fieldset>
<legend>姓名操作</legend>
姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName"/><br/>
名字:<input type="text" placeholder="请输入名字" v-model="user.lastName"/><br/>
</fieldset>
<fieldset>
<legend>计算属性和监视属性的演示</legend>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName1"/><br/>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName2"/><br/>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName3"/><br/>
</fieldset>
</template>
<script lang="ts">
import {computed, defineComponent, reactive,ref, watch, watchEffect} from 'vue';
export default defineComponent({
name:'App',
setup(){
const user = reactive({
firstName:'上官',
lastName:'婉儿'
})
// 通过计算属性方式,实现第一个姓名的显示
// 计算属性的函数如果只传入一个回调函数,表示的是get
const fullName1 = computed(()=>{
return user.firstName +'_'+ user.lastName
})
const fullName2 = computed({
get(){
return user.firstName+ '_'+user.lastName
},
set(val:string){
const names = val.split('_')
user.firstName = names[0]
user.lastName = names[1]
}
})
const fullName3 = ref('')
watch(user,({firstName,lastName})=>{
fullName3.value = firstName + '_' + lastName
},{immediate:true,deep:true})
watchEffect(()=>{
const names1 = fullName3.value.split('_')
user.firstName = names1[0]
user.lastName = names1[1]
})
return{
user,
fullName1,
fullName2,
fullName3
}
}
})
</script>
(7)生命周期
与vue2.x版本的生命周期相对应的组合式API
- beforeCreate
--->
使用setup
- created
--->
使用setup
- beforeMount
--->
使用onBeforeMount
- mounted
--->
使用onMounted
- beforeUpadte
--->
使用onUpdated
- updated
--->
使用onUpdated
- beforeDestory
--->
使用onBeforeUnmount
- destoryed
--->
使用onUnmounted
(8)自定义hook函数
- 使用Vue3的组合API封装的可复用的功能函数
- 自定义hook的作用类似于Vue2的mixin技术
- 自定义hook的优势:很清楚复用功能代码 的来源,清楚易懂
// hook函数的封装 新建hook文件夹 ---> 新建hook文件.ts
export default function(){
// 封装内容
return{
}
}
// hook函数的使用
// 引入hook函数
import useRequest from './hooks/useRequest'
const {返回值} = useRequest ()
(9)toRefs
把一个响应式对象转换为普通对象,该普通对象的每个property都是一个ref
应用:当从合成函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应的情况下对返回的对象进行分解使用
import {defineComponent, reactive, toRefs} from 'vue'
export default defineComponent({
name:'App',
setup(){
const state = reactive({
name:'张三',
age:19
})
// const state2 = toRefs(state)
const {name,age} = toRefs(state)
setInterval(()=>{
name.value += '^^'
},2000)
return{
name,
age
}
}
})
(10)ref获取元素
利用ref函数获取组件中的标签元素
功能需求:让输入框自动获取焦点
import {defineComponent,onMounted,ref} from 'vue'
export default defineComponent({
name:'App',
setup(){
const focusRef = ref<HTMLElement | null>(null)
onMounted(()=>{
focusRef.value&&focusRef.value.focus()
})
return{
focusRef
}
}
})
(11)shallowReactive与shallowRef
- shallowReactive:只处理了对象内最外层属性的响应式
- shallowRef:只处理了value的响应式,不进行reactive处理
- 什么时候用浅响应式?
1、如果有一个对象数据,结构比较深,但变化只是外层属性变化 ===> shallowReactive
2、如果有一个对象数据,后面会产生新的对象来替换 ===>shallowRef
setup(){
// 深度劫持(深监视) ---深度响应式
const m1 = reactive({
name:'喜洋洋',
age:25,
hobby:{
name:'打篮球',
type:'ball'
}
})
// 浅劫持(浅监视) ---浅响应式
const m2 = shallowReactive({
name:'喜洋洋',
age:25,
hobby:{
name:'打篮球',
type:'ball'
}
})
// 深度劫持(深监视) ---浅响应式 --- 做了reactive转换
const m3 = ref({
name:'喜洋洋',
age:25,
hobby:{
name:'打篮球',
type:'ball'
}}
)
// 浅劫持(浅监视) ---浅响应式
const m4 = shallowRef({
name:'喜洋洋',
age:25,
hobby:{
name:'打篮球',
type:'ball'
}
})
const update = ()=>{
// 更改m1的数据 ---reactive方式
// m1.name += '@@'
// m1.hobby.name += '##'
// 更改m2的数据 ---shallowReactive方式
// m2.name += '@@'
// m2.hobby.name += '##'
// 更改m3的数据 ---ref方式
// m3.value.name += '@@'
// m3.value.hobby.name += '##'
// 更改m4的数据 ---shallowRef方式
// m4.value.name += '@@'
// m4.value.hobby.name += '##'
// console.log(m3,m4);
}
return{
m1,
m2,
m3,
m4,
update
}
}
(12)shallowReadonly与readonly
- readonly
1、深度只读数据,如果是嵌套的也是不能修改 - shallowReadonly
2、浅只读数据,如果是嵌套的是能修改的
setup(){
const state = reactive({
name:'张三',
age:15,
hobby:{
year:5,
name:'足球'
}
})
// 只读数据,深度只读
const state1 = readonly(state)
// 只读数据,浅只读
const state2 = shallowReadonly(state)
const update = ()=>{
// state1.name += '李四'
// state1.hobby.name = '篮球'
// state2.name += '李四'
state2.hobby.name = '篮球'
}
return{
state1,
state2,
update
}
}
(13)toRaw与markRaw
- toRaw
作用:将一个由reactive(ref不能使用)生成的响应式对象转为普通对象。 - markRaw
作用:标记一个对象,使其永远不会再成为响应式对象。
setup(){
let car = reactive({
brand:'宝马',
color: 'red',
price: 15000
})
const carToRaw = ()=>{
// toRaw把响应式对象变成普通对象
car = toRaw(car)
car.price ++
console.log(car.price);
}
function addCar(){
let car = {
brand:'奔驰',
color: 'green',
price: 20000
}
// markRow让一个普通对象无法变成响应式
car = markRaw(car)
car.price ++
console.log(car.price);
}
return{
car,
carToRaw,
addCar
}
}
(14)toRef
- 为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者时同步的
- 区别ref:拷贝了一份新的数据值单独操作,更新时互不影响
- 应用:当要将某个prop的ref传递给复合函数时,toRef很有用
<template>
<h1>我是子组件</h1>
<h3>age:{{age}}</h3>
<h3>length:{{length}}</h3>
</template>
<script lang="ts">
import { defineComponent,Ref,computed,toRef } from 'vue';
function useGetLength(age:Ref){
return computed(()=>{
return age.value.toString().length
})
}
export default defineComponent({
name:'Child',
props:{
age:{
type:Number,
required:true
}
},
setup(props){
const length = useGetLength(toRef(props,'age'))
return {
length
}
}
})
</script>
(15)customRef
用于自定义一个ref,可以显示地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的track与用于触发响应的trigger,并返回一个带有set和get属性的对象
import {customRef, defineComponent,ref} from 'vue'
// 自定义hook防抖的函数
// value传入的数据,将来数据的类型不确定,所以用泛型,delay防抖的间隔时间,默认200毫秒
function useDebounceRef<T>(value:T,delay = 200){
// 准备一个存储定时器的id变量
let timeOutId:number
return customRef((track,trigger)=>{
return{
get(){
// 告诉追踪的数据
track()
return value
},
set(newValue:T){
clearTimeout(timeOutId)
timeOutId = setTimeout(()=>{
value = newValue
// 告诉更新页面
trigger()
},delay)
}
}
})
}
export default defineComponent({
name:'App',
setup(){
// const keyWord = ref('abc')
const keyWord = useDebounceRef('abc',500)
return{
keyWord
}
}
})
(16)provide和inject
实现跨层级组件间通信
App.vue
<template>
<h1>provide和inject</h1>
<h3>当前的颜色:{{color}}</h3>
<button @click="color = 'red' ">红色</button>
<button @click="color = 'green' ">绿色</button>
<button @click="color = 'yellow' ">黄色</button>
<hr>
<Son />
</template>
<script lang="ts">
import {defineComponent,provide,ref} from 'vue'
import Son from './components/Son.vue'
export default defineComponent({
name:'App',
components:{Son},
setup(){
const color = ref('red')
provide('color',color)
return{
color
}
}
})
</script>
GrandSon.vue
<template>
<h1 :style="{color}">我是GrandSon孙子组件</h1>
</template>
<script lang="ts">
import { defineComponent, inject } from 'vue';
export default defineComponent({
name:'GrandSon',
setup(){
const color = inject('color')
return{
color
}
}
})
</script>
(17)响应式数据判断方法
- isRef:检查一个值是否为ref对象
- isReactive:检查一个对象是否由reactive创建的响应式代理
- isReadonly:检查一个对象是否由readonly创建的只读代理
- isProxy:检查一个对象是否由reactive或者readonly创建的代理
三、其它新组合API
1、Fragment(片段)
- 在vue2中:组件必须有一个根标签
- 在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
- 好处:减少标签层级,减小内存占用
2、Teleport(瞬移)
提供了一种干净的方法,让组件的html在父组件界面外的特定标签下插入显示
语法:<teleport to=" "> </teleport>
<template>
<button @click="flag=true">打开一个对话框</button>
<teleport to='body'>
<div v-if="flag">
<div>这是一个对话框</div>
<button @click="flag=false">关闭对话框</button>
</div>
</teleport>
</template>
<script lang="ts">
import {defineComponent,ref} from 'vue'
export default defineComponent({
name:'buttonModal',
setup(){
let flag = ref(false)
return{
flag
}
}
})
</script>
3、Suspense组件的使用(不确定)
它允许我们的应用程序在等异步组件时候渲染一些内容,可以让我们创建一个平滑的用户体验
<template>
<h1>我是子级组件:Suspense</h1>
<h3>{{msg}}</h3>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
// vue3中动态引入组件的写法
export default defineComponent({
name:'App',
setup(){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
msg:'what are you doing'
})
},2000)
})
}
})
</script>```