组合式API
选项式api是将同一类选项放在一起,比如,所有data放在一起,methods放在一起。这种方式来说,各个功能上来说,各个功能的data和methods都混在一起了,比如功能1的data和功能2的data混在一起,功能1的methods和功能2的methods混在一起,就会显得很混乱,不易于维护。
组合式api是将同一类功能放在一起。比如功能1的data和功能1的methods放在一起,功能2的data和功能2的methods也放一起,按功能来组合。达到功能高内聚的效果,易于维护。
setup函数语法糖
原始的setup函数,要在函数中定义大量数据后,再将这些数据一个一个return返回
<script>
export default {
setup () {
console.log('setup执行了')
console.log(this)
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
return { msg , say}
}
}
</script>
数据多的情况下,看起来就会很乱,而且return语句中会很不好看,于是有了setup函数语法糖:
<script lang="ts" setup>
let name = "二狗";
let age = 20;
let tel = "18888888888";
let introduction = "一定要成为编程大牛";
function changeAge() {
age += 1;
console.log(age);
}
function showIntroduction() {
introduction = "一定要成为编程大牛!!!";
console.log(introduction);
}
</script>
这时的在script标签中的数据默认就当作全都return返回了。
响应式
ref
如果要让一个数据变成响应式的,就要让这个数据在定义的时候被ref函数包裹后再赋值给变量(要先从vue中引入ref函数)。这时的变量是一个RefImpl类型的了,要对原来的数据进行修改,要使用 变量.value 的方式,对这个value进行修改。
<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('张三')
let age = ref(18)
function changeName(){
name.value = '二狗'
}
function addAge(){
age.value++
}
</script>
ref定义对象类型数据:(底层其实使用了reactive)
let car2 = ref({brand:'宝马',price:100})
function changePrice2(){
car2.value.price += 20;
}
let chiikawa2 = ref([
{id:1,name:'吉伊',color:'白馒头'},
{id:2,name:'小八',color:'蓝裤衩头白猫'},
{id:3,name:'小八',color:'黄色怪叫兔'}
])
function change537_2(){
chiikawa2.value[2].name = "乌萨奇"
}
reactive
对象类型的数据,要包裹在reactive函数中再赋值给变量,此时的变量是一个代理对象,Proxy类型的。响应式后要更改对象的属性,就不是.value了,而是直接更改。
<script lang="ts" setup>
import { ref, reactive } from 'vue';
let car = reactive({brand:'奔驰',price:100})
let chiikawa = reactive([
{id:1,name:'吉伊',color:'白馒头'},
{id:2,name:'小八',color:'蓝裤衩头白猫'},
{id:3,name:'小八',color:'黄色怪叫兔'}
])
function changePrice(){
car.price += 20;
}
function change537(){
chiikawa[2].name = "乌萨奇"
}
</script>
reactive只能用来定义对象类型的响应式数据。
ref和reactive总结
- ref创建的变量必须要用.value的方式去获取和修改数据
- reactive重新分配一个新对象,会失去响应式,要想不失去响应式,可以使用Object.assign(变量名,新对象) 的方式,给目标变量一个新的对象值。
- 若需要一个基本类型的响应式数据,必须使用ref,若需要一个响应式对象,层级不深,ref和reactive都可以,若需要一个响应式对象,且层级较深,推荐使用reactive。
toRefs和toRef
如果要将对象中的数据解构取出使用,就要使用toRefs和toRef函数。
import { ref, reactive, toRefs ,toRef} from 'vue';
let car = reactive({brand:'奔驰',price:100})
let {brand,price} = toRefs(car)
let nl = toRef(car,'brand')
提取单个属性值,使用toRef,提取多个属性值,使用toRefs
计算属性
定义:如果一个要用的数据,而是由已有的属性(data中的属性)计算得来,那么可以将其作为计算属性
计算属性的变量也是一个ref的值,只要参与计算属性的值的计算的某个变量发生了变化,计算属性的值也会实时更新。
<template>
<div class="person">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
全名:<span>{{fullName}}</span><br>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
let firstName = ref('zhang')
let lastName = ref('san')
let fullName = computed(() => {
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
})
</script>
如果是要定义可读可写的计算属性:
let fullName2 = computed({
get(){
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
},
set(val){ //val参数就是此计算属性新修改的值
const x = val.split('-')
firstName.value=x[0]
lastName.value=x[1]
}
})
function changeFullName(){
fullName2.value = 'li-si'
}
watch监视
监视即在发现数据发生变化的时候,要进行某些逻辑。
在vue3中,watch只能监视以下四种数据:
ref定义的数据、reactive定义的数据、函数返回一个值(getter)、一个包含上述内容的数组。
<template>
<div>
<h2>sum的值为:{{ sum }}</h2>
<button @click="addSum">加一</button>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
let sum = ref(0)
function addSum(){
sum.value++
}
//监视
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了')
})
</script>
如果想要解除监视,可以用一个变量接收watch返回的值,它返回的值是一个函数,当调用这个函数后,就会结束对应的监视。
function addSum(){
sum.value++
if(sum.value >= 10){
stop()
}
}
//监视
const stop = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了')
})
监视对象类型的数据
若要监视对象类型的数据,通常要加入第三个参数,这个参数用于指定一些配置项,比如:是否深度监视(即检测到对象的属性的变化),是否在初始化的时候立即执行一次,等等。
<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++
}
function changePerson(){
person.value = {name:'李四',age:19}
}
watch(person,(newValue,oldValue)=>{ //要想设置为对监视的对象类型的数据的深度监视,要加配置项
console.log('person变化了',newValue,oldValue)
},{//第三个参数用于加其他配置项
deep:true,//开启深度监视
immediate:true//一来的时候先执行一次监视的函数中要执行的操作
})
</script>
此处的oldValue如果是直接改整个对象,oldValue就是之前的对象值。如果改的是对象的属性,oldValue和newValue是同一个值,因为还是同一个对象。
如果是使用reactive定义的对象类型的数据,默认是自动开启深度监视的
<script lang="ts" setup>
import { reactive,watch } from 'vue';
let person = reactive({
name:'张三',
age:18
})
function changeName(){
person.name += '~'
}
function changeAge(){
person.age++
}
function changePerson(){
Object.assign(person,{name:'李四',age:19})
}
watch(person,(newValue,oldValue)=>{ //reactive定义的对象类型的数据被监视的时候,默认是开启深度监视的
console.log('person变化了',newValue,oldValue)
})
</script>
如果只想监视对象的其中一个基本类型的属性,第一个参数写作一个getter函数,并且此时的监视,由于检测的是具体的属性,而不是对象。所以修改的时候,oldValue参数的值就是修改前的值
<script lang="ts" setup>
import { reactive,watch } from 'vue';
let person = reactive({
name:'张三',
age:18,
car:{
brand:'奔驰',
color:'red'
}
})
function changeName(){
person.name += '~'
}
function changeAge(){
person.age++
}
function changeCarBrand(){
person.car.brand = '宝马'
}
function changeCarColor(){
person.car.color = 'green'
}
function changeCar(){
person.car = {brand:'迈巴赫',color:'black'}
}
watch(()=>{return person.age},(newValue,oldValue)=>{
console.log('年龄变化为'+newValue+','+'旧值为'+oldValue)
})
</script>
如果要监视的对象a的属性还是一个对象b,如果直接写该属性作为第一个参数,也可以监视到其对象b的属性值的变化。但是如果对象b的整个对象值发生变化的时候,不能监视到。
所以,建议此对象b的监视也写作getter函数的形式作为第一个参数。但是此时要监视对象b的属性值的变化,要使用深度监视,也就是加上第三个参数配置项中的deep为true
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car发生变化了',newValue,oldValue)
},{deep:true})
如果要如此局部监视多个指定的属性,可以写作一个数组的方式
watch([()=>person.age,()=>person.car.color],(newValue,oldValue)=>{
console.log('发生变化了',newValue,oldValue)
},{deep:true})
WathEffect监视
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时,重新执行该函数
就是在函数中用到了那些数据,就检测那些数据,案例:
<template>
<div class="person">
<h2>需求:当水温达到60度,或者水位达到80cm的时候,给服务器发请求</h2>
<h2>当前水温:{{ temp }}℃</h2>
<h2>当前水位:{{ height }}cm</h2>
<button @click="changeTemp">改变温度</button>
<button @click="changeHeight">改变高度</button>
</div>
</template>
<script lang="ts" setup>
import {ref,watch, watchEffect} from 'vue';
let temp = ref(10);
let height = ref(0);
function changeTemp(){
temp.value += 10
}
function changeHeight(){
height.value += 10
}
//watch实现
watch([temp,height],(newValue)=>{
let [newTemp,newHeight] = newValue
if(newTemp >= 60 || newHeight >=80){
console.log('给服务器发请求')
}
})
//watchEffect实现
watchEffect(()=>{
if(temp.value >= 60 || height.value >= 80){
console.log('给服务器发请求')
}
})
</script>
关于ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 引用在html标签上,获取到的是真实的DOM元素;应用在组件标签上,获取到的是组件的实例对象
- 使用方式:
- 在相应的标签上,给上ref属性。
- 获取:let xxx = ref(),其中xxx指的是相应的标签的ref属性的属性值。
- 但是获取到的组件实例对象默认是受保护的,要是想向外暴露对应的实例对象中的属性,就使用defineExpose函数在组件中将对指定的属性暴露出去:defineExpose({属性,属性,……})
关于props的使用
要是子组件的某个属性想要由父组件来决定,以达到更好的复用效果。就要使用props属性
定义props属性:
<template>
<h2>
{{ a }}
</h2>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
//接收a
let x = defineProps(['a'])
console.log(x)
</script>
在defineProps函数中,定义的属性可以直接由template引用(如上方的a),但是不能直接在script中使用,要用一个变量接收后再到script中使用
父组件:
<PropsLearning a="你好"></PropsLearning>
如果是想引用父组件中的某个变量,比如有一个变量let hello:string = ‘你好’,就要在相应的props属性前加上 : ,表示将属性值 ” ” 中的内容解析为表达式。
如果想要限制props的类型,可以使用泛型的方式,写法如下:
defineProps<{b:string}>()
如果想要让b可有也可无,就在b后面加上一个?,例:
defineProps<{b?:string}>()
如果还想让其在不传值的时候,有一个默认值,要引入withDefaults函数:
import { defineProps,withDefaults } from 'vue';
// 定义组件的props,包括a和c+d,并设置c和d的默认值
const props = withDefaults(defineProps<{
a?: string; // 可选的字符串类型的prop
c?: string; // 可选的字符串类型的prop
d?: number; // 可选的数字类型的prop
}>(), {
c: 'hello', // 设置默认值
d: 100, // 设置默认值
});
如果要给默认值的属性是数组或者对象类型的,要使用箭头函数返回其要赋的值
Vue3生命周期
1、setup() : 开始创建组件,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
2、onBeforeMount() : 组件挂载到节点上之前执行的函数;
3、onMounted() : 组件挂载完成后执行的函数;
4、onBeforeUpdate(): 组件更新之前执行的函数;
5、onUpdated(): 组件更新完成之后执行的函数;
6、onBeforeUnmount(): 组件卸载之前执行的函数;
7、onUnmounted(): 组件卸载完成后执行的函数;
8、onActivated(): 被包含在 <keep-alive> 中的组件,会多出两个生命周期钩子函数,被激活时执行;
9、onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
10、onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
自定义Hooks
hooks用于将功能分离出到相应的ts文件中,组件要引入相应的功能的时候,引入相应的hooks的ts文件即可,更符合高内聚低耦合的理念。
例:
组件:
用解构表达式将相应的ts文件中导出的内容引用到组件中。
<template>
<div class="person">
<h2>当前求和为{{ sum }}</h2>
<button @click="add">点我sum+1</button>
<hr>
<img v-for="(dog,index) in dogList" :src="dog" :key="index">
<button @click="getDog">再来一只小狗</button>
</div>
</template>
<script lang="ts" setup>
import useDogs from '@/hooks/useDogs';
import useSum from '@/hooks/useSum';
const {sum,add} = useSum()
const {dogList,getDog} = useDogs()
</script>
<style>
img{
height: 100px;
margin-right: 10px;
}
</style>
hooks中的useDogs.ts:
ts数据要使用export导出,将要导出的数据封装为对象的形式return出去。
import { ref,reactive } from 'vue';
import axios from 'axios';
export default function(){
//数据
let dogList = reactive([
'<https://images.dog.ceo/breeds/kombai/Kombai-indian-Dog.jpg>'
])
//方法
async function getDog(){
try{
let result = await axios.get('<https://dog.ceo/api/breeds/image/random>')
dogList.push(result.data.message)
}catch(error){
alert(error)
}
}
return {dogList,getDog}
}
hooks中的useSum.ts:
import { ref,reactive } from 'vue';
import axios from 'axios';
export default function(){
//数据
let sum = ref(0)
//方法
function add(){
sum.value++
}
return {sum,add}
}