Vue3(二)计算属性Computed,监视属性watch,watchEffect,标签的ref属性,propos属性,生命周期,自定义hook
文章目录
- 一 、计算属性
- 1. 简写
- 2. 完整写法
- 二、监视watch
- 1. 监视【ref】定义的【基本类型】数据
- 2. 监视【ref】定义的【对象类型】数据
- 3. 监视【reactive】定义的【对象类型】数据
- 4. 监视【ref】或【reactive】定义的【对象类型】数据中的某个属性
- 5. 监视多个数据
- 总结
- 三、watchEffect
- 四、标签的ref属性
- 五、Propos
- 1.子组件接收,接收并保存
- 2. 限制接收的类型,限制必要性
- 3. 子组件给默认值
- 六、生命周期
- 七、自定义Hook
一 、计算属性
computed
:根据已有数据计算出新数据(和Vue2
中的computed
作用一致).
还是那个案例,当输入框里的内容变化时,全名跟着修改。
1. 简写
语法:computed(()=>{....})
// 引入computed
import { ref, computed } from "vue"let firstName = ref("zhang")
let lastName = ref("san")// 这么定义的fullName是一个计算属性,且是只读的(也就是简写形式)
let fullName = computed(() => {return firstName.value + "-" + lastName.value
})
2. 完整写法
语法:computed({ get() {...}, set(val) {... }, })
给按钮绑定事件,修改姓名
<button @click="changeFullName">全名改为:li-si</button>
代码:
let fullName = computed({get() {return firstName.value + "-" + lastName.value},set(val) {const [str1, str2] = val.split("-")firstName.value = str1lastName.value = str2},
})
二、监视watch
- 作用:监视数据的变化(和
Vue2
中的watch
作用一致) - 特点:
Vue3
中的watch
只能监视以下四种数据:
ref
定义的数据(基本数据类型,对象类型)。reactive
定义的数据。- 对象类型数据里的某个属性。
- 一个包含上述内容的数组。
1. 监视【ref】定义的【基本类型】数据
语法:watch(监视对象,回调函数)
监视对象直接写变量名即可,监视的是其value
值的改变。
import { ref, watch } from "vue"
// 数据
let sum = ref(0)
// 方法
function changeSum() {sum.value += 1
}
// 监视 watch(监视对象,回调函数);
const stopWatch = watch(sum, (newValue, oldValue) => {if (newValue > 10) {stopWatch()}
})
如果想停止监视,则接收watch
的返回值,watch返回的值是一个函数stopWatch
,调用该函数就停止监视sum
2. 监视【ref】定义的【对象类型】数据
import { ref, watch } from "vue"
// 数据
let person = ref({name: "tom",age: 18,
})
function changeName() {person.value.name += "~"
}
function changeAge() {person.value.age += 1
}
function changePerson() {person.value = { name: "李四", age: 50 }
}
watch(person, (newValue, oldValue) => {console.log("person变化", newValue, oldValue)
})
点击这三个按钮发现,只有当点击修改整个人
时,watch
监视才起作用。因为对于ref定义的对象数据类型,watch
监视的是地址值。若想监视对象里面的值,则开启深度监视
watch(person,(newValue, oldValue) => {console.log("person变化", newValue, oldValue)},{ deep: true } // 也可配置 immediate: true
)
另外,点击前两个按钮发现newValue
和oldValue
是同一个值。点击修改整个人
时,两个值才不一样。
因为修改名字和年龄只是修改了对象的属性,而修改整个人是修改了对象的地址。地址不一样了,newValue
和oldValue
值也就不一样了。
总结:
- 语法:
watch(监视对象,回调函数,{配置对象deep、immediate等等.....})
- 监视【ref】定义的【对象类型】数据时,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
- 若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。
若修改整个ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
3. 监视【reactive】定义的【对象类型】数据
把上边的代码改了改:
点击三个按钮,控制台打印的结果:
- 不用写
deep:true
,默认开启了深度监听。并且这个深度监听是不可关闭的。 - 注意到这三行打印的结果里
newValue
和oldValue
都是一样的值,这是因为person
对象的地址始终没有发生变化。
总结:
监视
reactive
定义的【对象类型】数据时,默认开启了深度监视且无法关闭。
4. 监视【ref】或【reactive】定义的【对象类型】数据中的某个属性
上边都是监视一个对象,这里监视对象的某属性。
let person = reactive({name: "张三",age: 18,car: {c1: "奔驰",c2: "宝马",},
})
// 方法
function changeName() {person.name += "~"
}
function changeAge() {person.age += 1
}
function changeC1() {person.car.c1 = "奥迪"
}
function changeC2() {person.car.c2 = "大众"
}
function changeCar() {
// reactive对象里属性对象可以这样改person.car = { c1: "雅迪", c2: "爱玛" }
}
只监视对象里的某一个基本数据类型age
// 第一个参数写成函数的形式,指定监听的属性;
// 如果写的是person,则无论点击哪个按钮,都会被监听到
watch(() => person.age, (newValue, oldValue) => {console.log("age改变", newValue, oldValue) // age改变 19 18}
)
监视对象里的对象类型属性car
watch(() => person.car,(newValue, oldValue) => {console.log("person.car变化了", newValue, oldValue)},{ deep: true }
)
不写deep:true
,则监视的是car
这个对象,当其地址发生变化时才会执行console.log
打印(也就是不会默认开始深度监听)。
写deep:true
,则也监视到car
内部的属性c1
,c2
。属性或对象发生变化都会打印
总结:
- 监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
- 监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
- 监视reactive响应式对象中的某个属性,且该属性是对象类型的(car),不会默认开始深度监听
5. 监视多个数据
还是4中的案例,需求是监视人的姓名和车
watch([() => person.name, person.car],(newValue, oldValue) => {console.log("person.car变化了", newValue, oldValue)},{ deep: true }
)
总结
- 对于
ref
定义的基本数据类型,监视的是其value
值- 对于
ref
定义的对象数据类型,监视的是对象的地址值
,若要监视对象里的属性,需要手动开启深度监视
- 对于
reactive
定义的对象数据类型,默认开启了深度监视
- 对于
ref
或reactive
对象类型里的某个属性,如果是该属性是对象(car),监视的是其地址值,不会默认开始深度监视
三、watchEffect
watch
对比watchEffect
:
(1) 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
(2) watch
:要明确指出监视的数据
(3) watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,就监视哪些属性)
import { ref, watch, watchEffect } from "vue"
// 数据
let temp = ref(0)
let height = ref(0)// 方法
function changeTemp() {temp.value += 10
}
function changeHeight() {height.value += 10
}// 用watch实现
watch([temp, height], (val) => {// 从value中获取最新的temp值、height值let [newTemp, newHeight] = valif (newTemp >= 50 || newHeight >= 20) {console.log("发送请求")}
})// 用watchEffect
watchEffect(() => {if (temp.value >= 50 || height.value >= 20) {console.log("发请求")}
})
四、标签的ref属性
ref
用于给标签打标识。
1. 用在普通DOM标签上
<template><div class="person"><h2 ref="title">hello</h2><h2 ref="title2">hello again</h2><h2>world</h2><button @click="showTitle">获取所有hello</button></div>
</template><script lang="ts" setup name="Person">
import { ref } from "vue"
// 似乎是解构赋值,这里接收的变量名需要和ref定义的一致
let title = ref()
let title2 = ref()
// let title1 = ref() 这样打印出title1是undefined
function showTitle() {console.log('ref', ref());console.log('title', title.value)console.log('title2', title2.value)
}
</script>
变量名需要的ref
的名称一致,否则undefined
(这个ref
打印出来也不知道是什么东西,看不懂,有点子神奇了)
2. 用在组件标签上
(竟然可以实现子组件给父组件传值)
父组件
<template><Person ref="ren" /><button @click="showInfo">点击获取组件信息</button>
</template><script lang="ts" setup name="APP">
import Person from "./components/Person.vue"
import { ref } from "vue"
let ren = ref()
function showInfo() {console.log(ren.value.name)console.log(ren.value.age)
}
</script>
子组件
// 不引入也行,definexxx都是一些宏函数,会自动引入
import { defineExpose } from "vue"
let name = "tom"
let age = 18
// 使用defineExpose将组件中的数据交给外部
defineExpose({ name, age })
五、Propos
propos用于父组件向子组件传值
父组件的数据:
import Person from './components/Person.vue'
import { reactive } from 'vue';
import { Persons } from './type';
// 父组件
let persons = reactive<Persons>([{ id: '12345', name: 'tom', age: 18 },{ id: '123456', name: 'jerry', age: 18 },{ id: '1234567', name: 'lucy', age: 18 }
])
1.子组件接收,接收并保存
<!--父组件--><Person :list=persons />
<!--子组件-->
<script>// 只接收defineProps(['list'])console.log(list[0]); // 没保存,直接读取会提示“找不到list”// 接收并保存let x = defineProps(['list'])console.log(x.list[0]); // Proxy(Object) {id: '12345', name: 'tom', age: 18}
</script>
2. 限制接收的类型,限制必要性
<!--父组件-->
<Person :list=persons /><Person :list="5" /> <!-- 不是Person类型会报错 --><Person /> <!-- 没有给子组件传递数据也会报错,解决方法:添加`?` --><!--子组件-->
<script>// 接收+限制类型,只能接收Persons类型的数据defineProps<{ list: Persons }>()
</script>
当添加一个?
之后,则父组件可传可不传。不传递也不会报错
defineProps<{ list?: Persons }>()
3. 子组件给默认值
import { withDefaults } from 'vue';
// 接收+限制类型+限制必要性+指定默认值
withDefaults(defineProps<{ list?: Persons }>(), {list: () => [{ id: '默认', name: '默认', age: 10 }]
})
六、生命周期
回顾vue2的生命周期,vue2(三) vue生命周期
Vue2
的生命周期
创建阶段:
beforeCreate
、created
挂载阶段:
beforeMount
、mounted
更新阶段:
beforeUpdate
、updated
销毁阶段:
beforeDestroy
、destroyed
Vue3
的生命周期
创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
常用:onMounted
(挂载完毕),onUpdated
(更新完毕),onBeforeUnmount
(卸载之前)
解释一下这个创建阶段,用代码表示就是:
<script lang="ts" setup name="Person">
import { onBeforeMount, onMounted, } from 'vue';console.log('创建');onBeforeMount(() => {console.log('挂载之前');
})onMounted(() => {console.log('挂载完毕');
})
</script>
七、自定义Hook
一直在说vue3是组合式API,每个功能的数据,方法都放在一起。目前在<script>
标签里还是上边写一堆功能的数据,下面写一堆功能的方法,和vue2没啥区别了。
hook其实就是将各个功能的数据和方法单独封装到一个hook文件中。以后修改功能就直接去这个hook文件里改就好了。
- 什么是
hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。 - 自定义
hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
现在页面上有两个功能:
src/hook/useSum.ts
import { ref, computed } from 'vue'
export default function(){
// 功能一的数据let sum = ref(0)
// 功能一的函数let bigSum = computed(() => {return 10 * sum.value})function addSum() {sum.value += 1}return {sum,addSum,bigSum}
}
src/hook/useDog.ts
import { reactive, ref, computed } from 'vue'
import axios from 'axios';
export default function(){
// 功能二的数据
let dogList = reactive<string[]>([])
// 功能二的函数
async function getDog() {try {// 发请求let { data } = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')// 维护数据dogList.push(data.message)} catch (error) {// 处理错误console.log(error.message)}
}
return {dogList,getDog}
}
组件内:
src/components/Person.vue
<template><div class="person"><h2> 求和为{{ sum }},扩大十倍后{{ bigSum }}</h2><button @click="addSum">sum+1</button><br><br><img v-for="(u, index) in dogList" :key="index" :src="(u as string)"><br><button @click="getDog">再来一只狗</button></div>
</template><script lang="ts" setup name="Person">
// 1. 引入
import useDog from '../hook/useDog.ts'
import useSum from '../hook/useSum.ts'
// 2. 解构获取信息
const { dogList, getDog } = useDog()
const { sum, addSum, bigSum } = useSum()
</script>