文章目录
前言
即然你都会了Ts,何不趁热打铁看看Vue3+Ts是怎么回事呢?
OptionsAPI与CompositionAPI
简介
- Vue2 的API设计是Options(配置)风格的
- Vue3 的API设计是Composition(组合)风格的
OptionsAPI的弊端
数据、方法,计算属性等,是分散在:data、methods、computed中的。增加或修改需求,就需要分别修改data、methods、computed,不利于维护和复用。
CompositionAPI的弊端
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
Setup
setup 是vue3中的一个新的配置项,值是一个函数,它是Composition API “表演的舞台”,组件中所用到的数据、方法、计算属性、监视…等等,均配置在setup中。特点如下:
- setup函数返回的对象中的内容,可直接在模板中使用。
- setup中访问this是underfined.
- setup函数会在berforeCreate之前调用,它是领先所有钩子执行的。
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script lang="ts">
export default {
name:"Person",
}
</script>
<script lang="ts" setup>
let name = 'zs'
let age = 18
let tel = 999
function changeName(){
name = '张三'//注意,此时这么修改name页面是不变化的
}
function changeAge(){
age += 1 //注意,此时这么修改age页面是不变化的
}
function showTel(){
alert(tel)
}
</script>
<style scoped>
</style>
上述代码中export default 中指定了组件的名字,如果想省略该处的script,同时还想指定组件名字。可以借助vite中的插件简化
- 第一步:npm i vite-plugin-vue-setup-extend -D
- 第二步:vite.config.ts
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),VueSetupExtend()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
配置完成后,就可以这么写了(如下)。
<script lang="ts" name="PresonView" setup>
let name = 'zs'
let age = 18
let tel = 999
function changeName(){
name = '张三'//注意,此时这么修改name页面是不变化的
}
function changeAge(){
age += 1 //注意,此时这么修改age页面是不变化的
}
function showTel(){
alert(tel)
}
</script>
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="showTel">查看联系方式</button>
</div>
</template>
<script lang="ts" name="PresonView" setup>
import {ref} from 'vue'
let name = ref('zs')
let age = ref(18)
let tel = 999
function changeName() {
name.value = '张三'
}
function changeAge() {
age.value += 1
}
function showTel() {
alert(tel)
}
</script>
<style scoped></style>
reactive创建:对象类型的响应数据
使用reactive 可以使用对象类型的数据变为响应数据。
<template>
<div class="person">
<h1>一辆{{ car.brand }}车,价值{{ car.price }}万元</h1>
<button @click="changePrice">修改汽车的价格</button>
<br>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">
{{ g.name }}
</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏的名字</button>
<br>
<h1>{{obj.a.b.c}}</h1>
<button @click="changeObj">测试</button>
</div>
</template>
<script setup>
import { reactive } from 'vue';
//数据
let car = reactive({ brand: '北汽', price: 18 })
let games = reactive([
{ id: 'game1', name: '王者荣耀' },
{ id: 'game2', name: '英雄联盟' },
{ id: 'game3', name: 'Apex' },
])
let obj =reactive({
a: {
b: {
c: 666
}
}
})
//方法
function changePrice() {
car.price = 7.89
}
function changeFirstGame() {
games[0].name = '流星蝴蝶剑'
}
function changeObj(){
obj.a.b.c = 999
}
</script>
<style>
.person {
padding: 10px;
}
</style>
ref创建:对象类型的响应数据
ref 创建对象类型的响应数据,修改数据需要加.value。
<template>
<div class="person">
<h1>一辆{{ car.brand }}车,价值{{ car.price }}万元</h1>
<button @click="changePrice">修改汽车的价格</button>
<br>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">
{{ g.name }}
</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏的名字</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
//数据
let car = ref({ brand: '北汽', price: 18 })
let games = ref([
{ id: 'game1', name: '王者荣耀' },
{ id: 'game2', name: '英雄联盟' },
{ id: 'game3', name: 'Apex' },
])
//方法
function changePrice() {
car.value.price = 7.89
}
function changeFirstGame() {
games.value[0].name = '流星蝴蝶剑'
}
</script>
<style>
.person {
padding: 10px;
}
</style>
ref和reactive的对比
-
宏观角度:
- ref 用来定义:基本类型数据,对象类型数据;
- reactive 用来定义:对象类型数据。
-
区别:
- ref 创建的变量必须使用.value (可以使用volar插件自动添加.value)。
- reactive 重新分配一个新对象,会失去响应式 (可以使用Object.assign 去整体替换,)
let car = ref({ brand: '北汽', price: 18 }) // let car = reactive({ brand: '北汽', price: 18 }) function changeCar(){ car.value = {brand:'奥迪',price:'40'} //Object.assign(car,{brand:'奔驰',price:39}) };
-
使用原则:
- 若需要一个基本类型的响应式数据,必须使用ref
- 若需要一个响应式对象,层级不深,ref、reative 都可以
- 若需要一个响应式对象,且层级较深,推荐使用 reactive
toRefs 与 toRef
- 作用:将一个响应式对象的每一个属性,转换为ref对象。
- 备注: toRefs与toRef功能一致,但toRefs 可以批量转换。
<template>
<div class="person">
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts" setup>
import { toRefs, toRef,reactive} from 'vue';
let person = reactive({ name: '张三', age: 18 })
let {name, age} = toRefs(person)
//toRef 只能一个一个取。
let n = toRef(person,'name');
function changeName(){
name.value += '^';
}
function changeAge(){
age.value += 1;
}
</script>
<style>
.person {
padding: 10px;
}
</style>
computed 计算属性
<template>
<div class="person">
姓:<input type="tetx" v-model="x" /><br>
名:<input type="text" v-model="m" /><br>
全名:<span>{{ fullName }}</span><br>
<button @click="changeFullName()">修改全名</button>
</div>
</template>
<script lang="ts" setup>
import { get } from 'http';
import { ref, computed } from 'vue';
let x = ref("zhang")
let m = ref("san")
//这么定义的fullName 是一个计算属性,且是只读的
// let fullName = computed(()=>{
// return x.value.slice(0,1).toUpperCase() + x.value.slice(1)+m.value;
// })
let fullName = computed({
get() {
return x.value.slice(0, 1).toUpperCase() + x.value.slice(1) + m.value;
},
set(val) {
const[str1, str2] = val.split('-')
x.value = str1
m.value = str2
}
})
function changeFullName(){
//此处调用触发了computed的set方法。
fullName.value = 'li-sa'
}
</script>
<style>
.person {
padding: 10px;
}
</style>
watch
- 作用:监视数据的变化
- 特点:vue3中的数据watch只能监视以下四种数据
1、ref定义的数据。
2、reactive定义的数据
3、函数返回一个值
4、一个包含上述内容的数组。
情况一(ref定义的基本类型数据)
监视ref定义的基本类型数据
<template>
<div class="person">
<p>
Ask a yes/no question
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes('?')) {
loading.value = true
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
} finally {
loading.value = false
}
}
})
</script>
<style>
.person {
padding: 10px;
}
</style>
情况二(ref定义的对象类型)
监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的地址值,若想监控对象内部的数据,要手动开启深度监视
<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="changePreson">换人</button>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
//数据
let person = ref({
name:'张三',
age:18,
})
function changeName(){
person.value.name += '^'
}
function changeAge(){
person.value.age += 1
}
function changePreson(){
person.value = {name:"李四",age:19};
}
//监视,情况一:监视ref定义的对象类型数据,监视是对象的地址值
//若想监视对象内部属性的变化,需要手动配置开始深度监视。
watch(person,(newVal,oldVal)=>{
console.log('person',newVal,oldVal);
},{
//深度监视
deep:true,
//立即监视
immediate:true,
})
</script>
<style>
.person {
padding: 10px;
}
</style>
情况三(reactive 隐式深度监视)
监视reactive 定义的,对象类型数据,且默认开启了深度监视。
<template>
<div class="person">
<h1>情况三:监视【reactive】定义的【响应式数据】</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePreson">换人</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from 'vue';
//数据
let person = reactive({
name: '张三',
age: 18,
})
function changeName() {
person.name += '^'
}
function changeAge() {
person.age += 1
}
function changePreson() {
Object.assign(person, { name: "李四", age: 19 });
}
watch(person, (newVal, oldVal) => {
console.log('person', newVal, oldVal);
})
</script>
<style>
.person {
padding: 10px;
}
</style>
情况四 (对象中的某项属性)
监视ref或reactive定义的【对象类型】数据中的某个属性,注意点如下:
- 若该属性值不是【对象类型】,需要写成函数形式
- 若该属性值是依然是【对象类型】,可直接编写,也可写成函数,不过建议写成函数。
结论:监视的要是对象里的属性,那么最好写函数式。
<template>
<div class="person">
<p>姓名:{{ person.name }}</p><br>
<p>年龄:{{ person.age }}</p><br>
<p>车辆:{{ person.cars.c1 }}、{{ person.cars.c2 }}</p><br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改车辆1</button>
<button @click="changeC2">修改车辆2</button>
<button @click="changeCar">修改所有车辆</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from 'vue';
let person = reactive({
name: '张三',
age: 23,
cars: {
c1: '奔驰',
c2: '宝马'
}
})
function changeName() {
person.name = '李四'
}
function changeAge() {
person.age += 1
}
function changeC1() {
person.cars.c1 = '大众'
}
function changeC2() {
person.cars.c2 = '丰田'
}
function changeCar() {
person.cars= { c1: '雅迪', c2: '爱玛' }
//Object.assign(person.cars,{c1: '雅迪', c2: '爱玛'})
}
//监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
// watch(()=> person.name,(newVal,oldVal)=>{
// console.log("person.name变化了", newVal, oldVal)
// })
watch(()=>person.cars,(newVal, oldVal)=>{
console.log("person.cars变化了", newVal, oldVal)
},{deep:true})
// watch(person.cars,(newVal,oldVal)=>{
// console.log("person.cars变化了", newVal, oldVal)
// })
</script>
<style>
.person {
padding: 10px;
}
</style>
情况五 (监视上述多个数据)
<template>
<div class="person">
<h1>情况五:监视上述的多个数据源</h1>
<p>姓名:{{ person.name }}</p><br>
<p>年龄:{{ person.age }}</p><br>
<p>车辆:{{ person.cars.c1 }}、{{ person.cars.c2 }}</p><br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改车辆1</button>
<button @click="changeC2">修改车辆2</button>
<button @click="changeCar">修改所有车辆</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from 'vue';
let person = reactive({
name: '张三',
age: 23,
cars: {
c1: '奔驰',
c2: '宝马'
}
})
function changeName() {
person.name = '李四'
}
function changeAge() {
person.age += 1
}
function changeC1() {
person.cars.c1 = '大众'
}
function changeC2() {
person.cars.c2 = '丰田'
}
function changeCar() {
person.cars= { c1: '雅迪', c2: '爱玛' }
//Object.assign(person.cars,{c1: '雅迪', c2: '爱玛'})
}
//监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
// watch(()=> person.name,(newVal,oldVal)=>{
// console.log("person.name变化了", newVal, oldVal)
// })
//监视,上述的多个数据源
watch([()=>person.name, ()=>person.cars.c1,],(newVal, oldVal)=>{
console.log("person.cars变化了", newVal, oldVal)
},{deep:true})
</script>
<style>
.person {
padding: 10px;
}
</style>
watchEffect
- 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
- Watch 对比 WatchEffect
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
- watch: 要明确指出监视的数据
- watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监听哪些属性。)
<template>
<div class="person">
<h2>水温:{{temp}}摄氏度</h2>
<h2>水位:{{high}}米</h2>
<button @click="changeTemp"></button>
<button @click="changeHeight"></button>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, watchEffect} from 'vue';
let temp = ref(0)
let high = ref(0)
function changeTemp(){
temp.value += 10
}
function changeHeight(){
high.value +=10
}
watchEffect(()=>{
if(temp.value>=60||high.value>=80){
console.log("给服务器发送请求");
}
})
</script>
<style>
.person {
padding: 10px;
}
</style>
标签的ref属性
作用 :用于注册模板引用
- 用在普通DOM标签上,获取的是DOM节点
- 用在组件标签上,获取的是组件实例对象
<template>
<div class='main_div'>
<button @click="renFun">让我看看</button>
<PersonVue ref="ren"></PersonVue>
</div>
</template>
<script setup>
import PersonVue from '@/components/Person.vue'
import {ref} from 'vue'
let ren = ref()
function renFun(){
console.log(ren.value);
}
</script>
<style>
.main_div{
background-color: dodgerblue;
}
</style>
<template>
<div class="person">
<h1>中国</h1>
<h2 ref="title2">北京</h2>
<h2>天安门</h2>
<button @click="outFun">输出</button>
</div>
</template>
<script lang="ts" setup>
import { ref,defineExpose } from 'vue'
//创建一个title2,用于存储ref标记的内容
let title2 = ref()
let a = ref(0)
let b = ref(1)
let c = ref(3)
function outFun(){
console.log(title2.value);
}
//设置向父级暴露哪些数据
defineExpose({a, b})
</script>
<style>
.person {
padding: 10px;
}
</style>
props
// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,
age:number
}
// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>
App.vue中代码:
<template>
<Person :list="persons"/>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {reactive} from 'vue'
import {type Persons} from './types'
let persons = reactive<Persons>([
{id:'e98219e12',name:'张三',age:18},
{id:'e98219e13',name:'李四',age:19},
{id:'e98219e14',name:'王五',age:20}
])
</script>
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 {defineProps} from 'vue'
import {type PersonInter} from '@/types'
// 第一种写法:仅接收
// const props = defineProps(['list'])
// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>
生命周期
- 概念:Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中vue会在适合的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子
-
Vue3
的生命周期创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
-
常用的钩子:
onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前) -
示例代码:
<template> <div class="person"> <h2>当前求和为:{{ sum }}</h2> <button @click="changeSum">点我sum+1</button> </div> </template> <!-- vue3写法 --> <script lang="ts" setup name="Person"> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' // 数据 let sum = ref(0) // 方法 function changeSum() { sum.value += 1 } console.log('setup') // 生命周期钩子 onBeforeMount(()=>{ console.log('挂载之前') }) onMounted(()=>{ console.log('挂载完毕') }) onBeforeUpdate(()=>{ console.log('更新之前') }) onUpdated(()=>{ console.log('更新完毕') }) onBeforeUnmount(()=>{ console.log('卸载之前') }) onUnmounted(()=>{ console.log('卸载完毕') }) </script>
自定义Hooks
-
什么是
hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。 -
自定义
hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
实例代码:
useSum.ts
中内容如下:
import { ref,computed } from 'vue'
export default function () {
//数据
let num = ref(0)
let bigNum = computed(()=>{
return num.value * 10
})
//方法
function plusFun() {
num.value += 1
}
//向外部提供东西
return { num, plusFun,bigNum }
}
useDog.ts
中内容如下:
import { reactive, onMounted} from 'vue'
import axios from 'axios';
export default function(){
//数据
let dogList = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_4052.jpg"
])
//方法
async function getDog() {
try {
let res = await axios.get("https://dog.ceo/api/breed/pembroke/images/random");
dogList.push(res.data.message)
} catch (error) {
alert(error)
}
}
onMounted(()=>{
getDog()
})
//向外部提供东西
return { dogList,getDog}
}
- 组件中具体使用:
<template>
<div class="person">
<h2>当前求和为:{{ num }},放大十倍{{ bigNum }}</h2>
<button @click="plusFun">点我sum+1</button>
<hr>
<img v-for="(item, index) in dogList" :src="item" :key="index" />
<br>
<button @click="getDog">再来一只小狗</button>
</div>
</template>
<script lang="ts" setup>
import useNum from '@/hooks/useNum';
import useDog from '@/hooks/useDog';
const { num, plusFun,bigNum } = useNum()
const { dogList, getDog } = useDog()
</script>
<style>
.person {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 10px;
}
img {
height: 100px;
margin-left: 10px;
}
</style>