前端框架Vue3(二)——Vue3核心语法之OptionsAPI与CompositionAPI与setup
Vue3核心语法
OptionsAPI与CompositionAPI
Vue2
的API
设计是Options
(配置)风格的。Vue3
的API
设计是Composition
(组合)风格的。
Options APl的弊端
Options
类型的API
,数据、方法、计算属性等,是分散在:data
、methods
、computed
中的,若想新
增或者修改一个需求,就需要分别修改:data
、methods
、computed
,不便于维护和复用。
Composition API的优势
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
拉开序幕的setup
setup概述
setup
是Vue3
中一个新的配置项,值是一个函数,它是Composition API
“表演的舞台”,组件中所用到的:数
据、方法、计算属性、监视等等,均配置在setup
中。
特点如下:
setup
函数返回的对像中的内容,可直接在模板中使用。setup
中访问this
是undefined
。setup
函数会在beforeCreate
之前调用,它是"领先"所有钩子执行的。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与ue2.x配置混用
- Vue2.x配置(
data
、methods
、computed
)中可以访问到setup
中的属性、方法 - 但在setup中不能访问到Vue2.x配置(
data
、methods
、computed
)。 - 如果有重名,setup优先。
- Vue2.x配置(
setup
不能是一个async
函数,因为返回值不再是return
的对象,而是promise
,模板看不到return
对象中的属性。
- 尽量不要与ue2.x配置混用
ref创建:基本类型的响应式数据
- 作用:定义响应式变量。
- 语法:
let xxx=ref(初始值)
。 - 返回值:一个
RefImpl
的实例对象,简称ref对象
或ref
,ref
对象的value
属性是响应式的, - 注意点:
JS
中操作数据需要:xxx.value
,但模板中不需要.value
,直接使用即可。- 对于
let name=ref('张三')
来说,name
不是响应式的,name.value
是响应式的。
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showSex">查看性别</button></div>
</template><script lang="ts">export default {name:'PersonComponent',}
</script>
<script lang="ts" setup name='PersonComponent'>
import { ref } from 'vue';const name = ref("张三")const age = ref(18)const sex = "男"// 方法function changeName() {name.value = "王五"console.log(name)}function changeAge() {age.value+=1}function showSex() {alert(sex)}
</script>
<style scoped>
.person{background-color: pink;box-shadow: 0 0 10px ;border-radius: 10px;padding: 20px;
}
button{margin-right: 10px;
}
</style>
reactive创建:对象类型的响应数据
<template><div class="person"><h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2><button @click="changePrice">修改汽车的价格</button><br><h2>游戏列表:<ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></h2><button @click="changeFirstGameName">修改游戏</button></div>
</template><script lang="ts" setup name="Person">
import { defineComponent, reactive } from 'vue';const car = reactive({brand: 'Benz',price: 100
});const games = reactive([{ id: "1", name: "王者荣耀" },{ id: "2", name: "英雄联盟" },{ id: "3", name: "和平精英" }
])console.log(car)function changePrice() {car.price += 10;
}function changeFirstGameName() {games[0].name = "羊了个羊"
}
defineComponent({name: 'PersonInfo'
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
ref创建:对象类型的响应数据
<template><div class="person"><h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2><button @click="changePrice">修改汽车的价格</button><br><h2>游戏列表:<ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></h2><button @click="changeFirstGameName">修改游戏</button></div>
</template><script lang="ts" setup name="Person">
import { defineComponent, ref } from 'vue';const car = ref({brand: 'Benz',price: 100
});const games = ref([{ id: "1", name: "王者荣耀" },{ id: "2", name: "英雄联盟" },{ id: "3", name: "和平精英" }
])console.log(car)function changePrice() {car.value.price += 10;}function changeFirstGameName() {games.value[0].name = "羊了个羊"}
defineComponent({name: 'PersonInfo'
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
ref对比reactive
- 宏观角度看:
ref
用来定义:基本类型数据、对象类型数据:reactive
用来定义:对象类型数据。
- 区别:
ref
创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。reactive
重新分配一个新对象,会失去响应式(可以使用Object.assign
去整体替换)。
- 使用原则:
- 若需要一个基本类型的响应式数据,必须使用
ref
。 - 若需要一个响应式对象,层级不深,
ref
、reactive
都可以。 - 若需要一个响应式对象,且层级较深,推荐使用
reactive
。
- 若需要一个基本类型的响应式数据,必须使用
<template><div class="person"><h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2><button @click="changePrice">修改汽车的价格</button><button @click="changeBrand">修改汽车的品牌</button><button @click="changeCar">修改汽车</button><hr><h2>当前求和为:{{ sum }}</h2><button @click="changeSum">sum+1</button></div>
</template><script lang="ts" setup name="Person">
import { defineComponent, ref } from 'vue';// const car = reactive({
// brand: 'Benz',
// price: 100
// });
const car = ref({brand: 'Benz',price: 100
});
const sum = ref(0);// function changePrice() {
// car.price += 10;
// }
// function changeBrand() {
// car.brand = 'hongqi';
// }
function changePrice() {car.value.price += 10;}
function changeBrand() {car.value.brand = 'hongqi';}function changeCar() {// Object.assign(car, {// brand: 'Aodi',// price: 10// });car.value = {brand: 'Aodi',price: 10};}
function changeSum() {sum.value += 1;}
defineComponent({name: 'PersonInfo'
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
toRefs与toRef
- 作用:将一个响应式对象中的每一个属性,转换为
ref
对象。 - 备注:
toRefs
与toRef
功能一致,但toRefs
可以批量转换 - 语法如下:
<template><div class="person"><h2>name:{{ name }}</h2><h2>age:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button></div>
</template><script lang="ts" setup name="Person">import { defineComponent, reactive, toRefs } from 'vue';const person = reactive({name: '张三',age: 18,})defineComponent({name: 'PersonComponent',})const {name,age} = toRefs(person);function changeName() {name.value += '三';}function changeAge() {age.value += 1;}</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
计算属性(computed)
<template><div class="person">姓:<input type="text" v-model="firstname"><br>名:<input type="text" v-model="lastname"><br><button @click="changeFullName()">将全名改为li-si</button>全名:<span>{{ fullname}}</span></div>
</template><script lang="ts" setup>
import { computed, ref } from 'vue';const firstname = ref('张');
const lastname = ref('三');
// 这样定义的fullname是一个计算属性,且是只读的
// const fullname=computed(() => {
// return firstname.value.slice(0,1).toUpperCase() + firstname.value.slice(1)+'-'+ lastname.value;
// });
const fullname = computed( {get(){return firstname.value.slice(0,1).toUpperCase() + firstname.value.slice(1)+'-'+ lastname.value;},set(val){const [str1,str2]=val.split('-');firstname.value=str1lastname.value=str2}
});function changeFullName() {fullname.value='李-四'
}
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
watch
- 作用:监视数据的变化(和
Vue2
中的watch
作用一致) - 特点:
Vue3
中的watch
只能监视以下四种数据:ref
定义的数据。reactive
定义的数据。- 函数返回一个值(
getter
函数)。 - 一个包含上述内容的数组。
我们在Vue3
中使用watch
的时候,通常会遇到以下几种情况:
*情况一
监视ref
定义的【基本类型】数据:直接写数据名即可,监视的是其value
值的改变。
<template><div class="person"><h1>情况一:监视【ref】定义的【基本类型】数据</h1><h2>当前求和为:{{ sum }}</h2><button @click="changeSum">点击sum+1</button></div>
</template><script lang="ts" setup>
import { ref, watch } from 'vue';const sum=ref(0)function changeSum() {sum.value+=1;
}
// 监视
watch(sum,(newValue,oldValue)=>{console.log('sum被修改了',newValue,oldValue)
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
*情况二
监视ref
定义的【对像类型】数据:直接写数据名,监视的是对像的【地址值】,若想监视对像内部的数据,要
手动开启深度监视。
注意:
- 若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。 - 若修改整个
ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
<template><div class="person"><h1>情况二:监视【ref】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改人</button></div>
</template><script lang="ts" setup name="Person">
import { ref, watch } from 'vue';const person = ref({name:'张三',age:18
})function changeName(){person.value.name += '五'
}function changeAge(){person.value.age += 1
}function changePerson(){person.value = {name:'李四',age:90}
}
// 监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内容属性的变化,需要手动开启深度监视
// watch(person,(newValue,oldValue)=>{
// console.log('person变化了',newValue,oldValue)
// })
// 监视`ref`定义的【对像类型】数据:直接写数据名,监视的是对像的【地址值】,若想监视对像内部的数据,要手动开启深度监视
// watch的第一个参数是监视的数据
// watch的第二个参数是监视的回调
// watch的第二个参数是配置对象()
watch(person,(newValue,oldValue)=>{console.log('person变化了',newValue,oldValue)
},{deep:true,immediate:true})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
*情况三
监视reactive
定义的【对象类型】数据,且默认开启了深度监视。
<template><div class="person"><h1>情况三:监视【ref】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改人</button></div>
</template><script lang="ts" setup name="Person">
import { reactive, watch } from 'vue';const person = reactive({name:'张三',age:18
})function changeName(){person.name += '五'
}function changeAge(){person.age += 1
}function changePerson(){Object.assign(person,{name:'王五',age:19})
}
//监视,情况三:监视`reactive`定义的【对象类型】数据,且默认开启了深度监视。
watch(person,(newValue,oldValue)=>{console.log('person被修改了',newValue,oldValue)
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
*情况四
监视ref
或reactive
定义的【对象类型】数据中的某个属性,注意点如下:
- 若该属性值不是【对象类型】,需要写成函数形式。
- 若该属性值是依然是【对象类型】,可直接编,也可写成函数,不过建议写成函数。
结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,
需要手动开启深度监视。
<template><div class="person"><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeFirstCar">修改第一台车</button><button @click="changeSecondCar">修改第二台车</button><button @click="changeCar">修改车</button></div>
</template><script lang="ts" setup name="Person">
import { reactive, watch } from 'vue';
const person = reactive({name: '张三',age: 18,car:{c1:"Benz",c2:"BMW"}
});function changeName() {person.name += '三';}function changeAge() {person.age += 1;}function changeFirstCar() {person.car.c1 ='hongqi'}function changeSecondCar() {person.car.c2 ='Aodi'}function changeCar() {person.car.c1 ='hongqi';person.car.c2 ='Aodi';}watch(()=>{return person.name},(newValue, oldValue) => {console.log('person.name被修改了', newValue, oldValue);})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
*情况五
情况五:监视多个数据
<template><div class="person"><h1>情况五:监视多个数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeFirstCar">修改第一台车</button><button @click="changeSecondCar">修改第二台车</button><button @click="changeCar">修改车</button></div>
</template><script lang="ts" setup name="Person">
import { reactive, watch } from 'vue';
const person = reactive({name: '张三',age: 18,car:{c1:"Benz",c2:"BMW"}
});function changeName() {person.name += '三';}function changeAge() {person.age += 1;}function changeFirstCar() {person.car.c1 ='hongqi'}function changeSecondCar() {person.car.c2 ='Aodi'}function changeCar() {person.car.c1 ='hongqi';person.car.c2 ='Aodi';}// 监视,情况五:监视上述多个数据watch([()=>person.name,()=>person.car.c1] ,(newValue, oldValue) => {console.log("person.car被修改了", newValue, oldValue);},{deep:true})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
watchEffect
- 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
watch
对比watchEffect
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch
:要明确指出监视的数据watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
示例代码:
<template><div class="person"><h2>需求:当水温达到60度,或水位达到80cm,给服务器发送请求</h2><h2>当前水温为:{{ temp }}°C</h2><h2>当前水位为:{{ height }}cm</h2><button @click="changeTemp">点我temp+10</button><button @click="changeHeight">点我height+10</button></div>
</template><script lang="ts" setup name="Person">
import { ref, watchEffect } from 'vue';
const temp = ref(10);
const height = ref(0);
function changeTemp() {temp.value+=10
}
function changeHeight() {height.value+=10
}
// 监视 watch实现
/*watch([temp,height],(value)=>{
const [newTemp,newHeight]=value
console.log(newTemp,newHeight)
if(newTemp>=60||newHeight>=80){
console.log("预警!")
}
})*/// 监视 watchEffect实现
watchEffect(()=>{if(temp.value>=60||height.value>=80){console.log("预警!")}
})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
【标签的ref属性】
作用:用于注册模板引用。
- 用在普通
DOM
标签上,获取的是DOM
节点。 - 用在组件标签上,获取的是组件实例对象。
用在普通DOM
标签上:
<template><div class="person"><h1>中国</h1><h2 ref="title2">河南</h2><h3>前端</h3><button @click="showLog">点我输出h2</button></div>
</template><script lang="ts" setup name="Person">
import { defineExpose, ref } from 'vue';
//创建一个title2,用于存储ref标记的内容
const title2 = ref()
const a = ref(1)
const b = ref(2)
const c = ref(3)
function showLog() {console.log(title2.value)
}
defineExpose({a,b,c})
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
回顾TS中的接口泛型自定义类型
<template><div class="person">
<h3>{{ person }}</h3>
<h3>{{ personList }}</h3></div>
</template><script lang="ts" setup name="Person">
import { type PersonInter, type Persons } from '@/types';const person:PersonInter={id:'1',name:'张三',age:18}
const personList:Persons=[{id:'1',name:'张三',age:18},{id:'2',name:'李四',age:20},{id:'3',name:'王五',age:30}
]
</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
props的使用
//Person.vue
<template><div class="person"><ul><li v-for="item in list" :key="item.id">{{ item.name }}--{{ item.age }}</li></ul></div>
</template><script lang="ts" setup name="Person">
import { withDefaults } from 'vue';
import {type Persons } from '../types/index';// 只接收list
// defineProps(['list'])// 接收list+限制类型
// defineProps<{list:Persons}>()//接收list+限制类型+限制必要性+指定默认值
// withDefaults(defineProps<{list?:Persons}>(),{
// list:()=>[{id:'1',name:'haha',age:18}]
// })// 接收a,同时将props保存起来
// const x=defineProps(['a'])
// console.log(x)</script><style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
//App.vue
<template><Person a="haha" :list="personList"/>
</template><script lang="ts" setup name="App">
import Person from './components/Person.vue';
import {reactive} from 'vue';
import {type Persons} from "@/types";
const personList=reactive<Persons>([{id:'1',name:'李四',age:18,x:10},{id:'2',name:'王五',age:20,},{id:'3',name:'张三',age:35,}
])
</script>
生命周期
人的生命周期:
【时刻】 | 【要做的事】 |
---|---|
出生 | 哭 |
经历 | 哭、笑 |
死亡 | 遗嘱 |
组件的生命周期:
【时刻】 | 【调用特定的函数】 |
---|---|
创建 | created |
挂载 | mounted |
更新 | |
销毁 |
- 概念:
Vue
组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue
会在合适的时机,调用特定的函
数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子 - 规律:
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。 Vue2
的生命周期- 创建阶段:
beforeCreate
、created
- 挂载阶段:
beforeMount
、mounted
- 更新阶段:
beforeUpdate
、updated
- 销毁阶段:
beforeDestroy
、destroyed
- 创建阶段:
Vue3
的生命周期- 创建阶段:
setup
- 挂载阶段:
onBeforeMount
、onMounted
- 更新阶段:
onBeforeUpdate
、onUpdated
- 销毁阶段:
onBeforeUnmount
、onUnmounted
- 创建阶段:
- 常用的钩子:
onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前) - 示例代码:
<template><div class="person"><h2>当前求和为:{{ sum }}</h2><button @click="add">点我sum+1</button></div>
</template><script lang="ts" setup name="Person">
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from 'vue'
// 数据
const sum=ref(0)// 方法
function add() {sum.value+=1
}// 创建
console.log("子--创建成功")
// 挂载
onBeforeMount(()=>{console.log("子--挂载成功")
})
// 挂载完毕
onMounted(()=>{console.log("子--挂载完毕")
})
// 更新前
onBeforeUpdate(()=>{console.log("子--更新前")
})
// 更新后
onUpdated(()=>{console.log("子--更新后")
})
// 销毁前
onBeforeUnmount(()=>{console.log("子--销毁前")
})
// 销毁后
onUnmounted(()=>{console.log("子--销毁后")
})
</script>
<style scoped>
.person {background-color: pink;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}
button {margin-right: 10px;
}
</style>
自定义hooks
import { computed, onMounted, ref } from 'vue'
export default function () {// 数据const sum = ref(0)const bigSum=computed(()=>{return sum.value*10})// 方法function add() {sum.value += 1}// 钩子onMounted(() => {add()})
// 向外部提供东西return {sum,add,bigSum}
}
import axios from 'axios'
import { reactive,onMounted } from 'vue'export default function (){// 数据
const dogList = reactive(['https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
])
// 方法
async function addDog() {try {const result = await axios.get('https://dog.ceo/api/breeds/image/random')if (result.data && result.data.message) {dogList.push(result.data.message)}} catch (error) {console.error('获取狗狗图片失败:', error)}
}
// 钩子
onMounted(() => {addDog()
})
//向外部提供东西
return {dogList, addDog}
}
import { computed, onMounted, ref } from 'vue'
export default function () {// 数据const sum = ref(0)const bigSum=computed(()=>{return sum.value*10})// 方法function add() {sum.value += 1}// 钩子onMounted(() => {add()})
// 向外部提供东西return {sum,add,bigSum}
}