ref, reactive, toRefs
ref定义简单的基本数据类型,如number,string等,更改其值时需对其value属性更改
reactive定义对象,对其更改时直接对其key更改即可
toRefs应用场景:对reactive内的属性解构时,解构出来的属性并不是响应式的,这时用toRefs包裹reactive对象
<template>
<div>
<h1>{{ num }}</h1>
<button @click="changeNum">changeNum</button>
<hr>
<h2>{{ person.name }}</h2>
<button @click="changeName">changeName</button>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue'
const num = ref(0)
const changeNum = () => {
num.value += 10
}
const person = reactive({
name: 'czc',
age: 20
})
const changeName = () => {
person.name = person.name === 'czc' ? 'sb' : 'czc'
}
</script>
toRefs
//..
const obj = reactive({
a: 10
})
return {
...toRefs(obj)
}
computed
computed首先要引入,然后分为两种用法
- 参数为一个函数,值为函数的返回值
import {ref, reactive, computed} from 'vue'
const person = reactive({
name: 'czc',
age: 20
})
const computed1 = computed(() => person.name + '666')
- 参数为一个对象,里面有
get和set,像vue2的watch,感觉这用法有点奇葩,了解就好
const computed2 = computed({
get() {
return person.name
},
set(val) {
return person.name = person.name + val
}
})
watch
watch也要引入,有以下几种使用方法
- 监听
ref定义的数据(一个或多个)
watch([num, num2], (newval, oldval) => {
//...
})
- 监听
reactive定义的整个对象,这里有几个点需要知道:
- 默认开启深度监听
deep:true - 获取不到
oldValue
const obj = reactive({
a: 10,
b: 20
})
const changeObj = () => {
obj.a += 10
obj.b += 10
}
watch(obj, (newVal, oldVal) => {
console.log('obj对象被监听---', newVal, oldVal);
})
或者是这种,默认不开启深度监听,没啥用
watch(() => obj, (newVal, oldVal) => {
console.log('obj对象被监听---', newVal, oldVal);
},{deep: true})
- 监听对象的某个属性,可以获取oldValue
watch(() => obj.a, (newVal, oldVal) => {
console.log('obj对象被监听---', newVal, oldVal);
},{deep: true})
</script>
watchEffect
watchEffect也是一个侦听器,默认开启类似watch的immediate: true,但不具备深度监听功能。
const obj = reactive({
a: 10,
b: 20
})
const changeObj = () => {
obj.a += 10
obj.b += 10
}
// watch(() => obj.a, (newVal, oldVal) => {
// console.log('obj对象被监听---', newVal, oldVal);
// },{deep: true})
const effect = () => {
console.log(obj.a);
}
watchEffect(effect)
watchEffect默认会监听组件的生命周期,在组件卸载时停止监听,但在特定情况下我们也能手动进行停止监听,watchEffect的返回值也是一个函数,调用这个函数就可以停止监听。
const effect = () => {
console.log(obj.a);
}
const stopWatching = watchEffect(effect)
setTimeout(() => {
stopWatching()
}, 2000);
watchEffect里的第一个参数是一个函数,这里我们称为effect函数,而effect函数中可以传入一个参数onInvalidate,这个参数同样也是一个函数。
侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。 当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时;
- 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
用一个别人文章里的例子:
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (场景:有一个页码组件里面有5个页码,点击就会异步请求数据。于是做一个监听,监听当前页码,只要有变化就请求一次。问题:如果点击的比较快,从1到5全点了一遍,那么会有5个请求,最终页面会显示第几页的内容?第5页?那是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。
我们要注意一个关键词,失效, 这个onInvalidate函数是用来清理失效时的回调,像上面那个例子,当我们监听到多个页码的ajax请求,前一个请求由于请求时间长,已经被后一个请求覆盖,那么这时onInvalidate函数就发挥作用,取消这个失效回调。具体代码可以看这篇文章
Vue3中 watch、watchEffect 详解 - 掘金 (juejin.cn)
defineProps
defineProps用来代替vue2\3.0 中的props, 作为一个宏命令,defineProps是无需引入的。
- 基本用法
defineProps({
msg: {
type: String,
default: "sb",
required: false
}
})
- 在ts中使用,使用withDefault设置默认值
interface Props { tableData?: any }
const props = withDefaults(defineProps<Props>(),
{ tableData: () => [] }
)
defineEmits
defineEmits用来代替emits,是宏命令,无需引入
// app.vue
<HelloWorld msg="Vite + Vue" @hello="getData"/>
const getData = (count) => {
data.value = count
console.log('---' + count);
}
// helloWorld.vue
const emits = defineEmits(['hello'])
const emitData = () => {
emits('hello', 222)
}
useAttrs
我们知道,当子组件为单根节点时,父节点传入的attributes是默认作用于根节点的。在vue2和vue3中,我们都可以使用inheritAttrs: false禁止自动透传,但在setup语法糖中,我们需要在额外的script标签声明这一选项。
<script>
// 使用普通的 <script> 来声明选项
export default {
inheritAttrs: false
}
</script>
<script setup>
// ...setup 部分逻辑
</script>
禁用透传后,我们可以通过$attrs获得所有除props,emit的选项,而在setup语法中,我们使用useAttrs获取这一对象。需要提前引入。
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
在多根节点的继承中,我们需要显式指定attrs继承,否则会报错
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
useSlots
暂时没明白这个的使用场景
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld>
<div>默认插槽</div>
<template v-slot:footer>
<div>具名插槽footer</div>
</template>
</HelloWorld>
</template>
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
// 在js中访问插槽默认插槽default、具名插槽footer
console.log(slots.default)
console.log(slots.footer)
</script>
<template>
<div>
<!-- 在模板中使用插槽 -->
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
顺便复习一下具名作用域插槽
具名插槽是在有多个slot作用时,必须指明slot的name否则无法分辨。v-slot:name
作用域插槽是通过slot让子组件向父组件传递数据 v-slot="name",name对象上包含了子组件通过v-bind传递过来的数据。
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer" :slotName="slotName"></slot>
</footer>
</div>
</template>
<script setup>
const slotName = "sb"
</script>
<style scoped>
</style>
<SlotDemo>
<template #[slotName]>
<h1>我是HEADER</h1>
</template>
<template #footer="footerProps">
<h1>{{footerProps.slotName}}</h1>
</template>
<h1>我是默认</h1>
</SlotDemo>
顶层await支持
setup语法糖顶层支持await语法,编译后会变成await setup
mitt库
Vue3移除了off等自带的自定义事件相关方法,因此在vue3中他推荐我们下载mitt库来使用事件总线传递数据。就三个api on emit off
创建mitt实例对象
// mitt库默认导出的是一个函数,我们需要执行它从而得到事件总线的对象
/* mitt.ts */
// 这里我们在ts中暴露这个事件总线对象
import mitt from "mitt";
const emitter = mitt()
export default emitter;
emit
import emitter from "../utils/mitt.ts";
const slotName = ref("sb")
const emitterEmit = () => {
emitter.emit('slotEmit', slotName.value)
}
onMounted(() => {
emitterEmit()
})
on
onMounted(() => {
emitter.on('slotEmit', (e) => {
console.log('-----'+e);
})
})
off
// 导入事件总线
import emitter from "./utils/mitt.ts";
emitter.off("test"); // 取消监听
// 取消所有的mitt事件
emitter.all.clear()
待完善