默认插槽
使用自定义组件,在组件标签对中添加内容
vue
<slot-child>this is parent slot</slot-child>
中间添加的内容会展示到自定义组件中的 <slot>默认内容</slot>
标签中,如果没传内容则展示默认内容
具名插槽
子组件中可以有多个坑位让父组件定义内容,使用<slot name="s1">
不同的name来做坑位的区分
Vue
<slot name="s1">默认内容</slot>
<slot name="s2">默认内容2</slot>
父组件可以在自定义组件中,定义属性v-slot:name
来填充name指定的坑位,可以简写为#name
vue
<slot-child #s2 或者 v-slot:s2>
this is s2
</slot-child>
多个坑位的时候,可以在template中指定坑位名称来定义坑位内容
vue
<template>
<slot-child>
<template #s2>this is s1</template>
<template #s1>this is s2</template>
</slot-child>
</template>
作用域插槽
数据在子组件中,但是需要在父组件中决定子组件展示成什么样
首先子组件中定义了数据,以及slot坑位,将数据作为slot的prop
vue
<template>
<div>
<h1>slot child</h1>
<slot name="s1" :users="users"></slot>
<slot name="s2" :users="users"></slot>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const users = ref([
{ name: '张三', age: 18 },
{ name: '李四', age: 20 },
{ name: '王五', age: 22 }
])
</script>
然后在父组件中接收prop数据
vue
<template #s1="props"> // 子组件的prop属性都会在父组件的props中接收
父组件示例如下
vue
<template>
<slot-child>
this is s1,接收子组件所有的prop到props中,从props中拿到users数据遍历,同时可以给插槽起个名字(具名插槽)
<template #s1="props"> // 等同于 v-slot:s1="props"
<ul>
<li v-for="user in props.users" :id="user.name">{{user.name}} - {{user.age}}</li>
</ul>
</template>
</slot-child>
<slot-child>
this is s2
<template #s2="props">
<ol>
<li v-for="user in props.users" :id="user.name">{{user.name}} - {{user.age}}</li>
</ol>
</template>
</slot-child>
</template>
shallowRef & shallowReactive
先看下ref的示例
vue
<template>
<div>
<h1>当前求和为 {{sum}}, name:{{person.name}} age:{{person.age}}</h1>
<button @click="changeSum">sum+1</button>
<button @click="changeName">changeName</button>
<button @click="changeAge">changeAge</button>
<button @click="changePerson">changePerson</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
let sum = ref(0)
let person = ref({
name: 'why',
age: 18
})
function changeSum() {
sum.value += 1
}
function changeName() {
person.value.name += '~'
}
function changeAge() {
person.value.age += 1
}
function changePerson() {
person.value = { name: 'kobe', age: 38 }
}
</script>
将ref改为shallowRef,我们发现sum能改,person中的内容改了没反应,能改整个person,说明shallowRef只对第一层响应式有效
typescript
import { shallowRef } from 'vue'
let sum = shallowRef(0)
let person = shallowRef({
name: 'why',
age: 18
})
reactive的示例
vue
<template>
<div>
<h1>当前汽车是 {{car}}</h1>
<button @click="changeName">changeName</button>
<button @click="changeColor">changeColor</button>
<button @click="changeSize">changeSize</button>
</div>
</template>
<script setup lang="ts">
// reactive对象没办法直接修改car,只能使用Object.assign来改内容
import { reactive } from 'vue'
let car = reactive({
name: '奔驰',
options: {
color: "red",
size: "big"
}
})
function changeName() {
car.name = '宝马'
}
function changeColor() {
car.options.color = 'blue'
}
function changeSize() {
car.options.size = 'small'
}
</script>
改为shallowReactive之后,只有修改名字生效。
通过shallowXXX来绕开深度响应,浅层次api创建的状态只在其顶层做响应式,避免每一个都做响应式带来的性能成本
readonly & shallowReadonly
写一个sum求和示例
vue
<template>
<div>
<h1>求和为:{{sum}}</h1>
<button @click="changeSum">sum+1</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const sum = ref(0)
function changeSum() {
sum.value++
}
</script>
只读示例如下
vue
<template>
<div>
<h1>求和为:{{sum}} sum2: {{sum2}}</h1>
<button @click="changeSum">sum+1</button>
<button @click="changeSum2">sum2+1</button>
</div>
</template>
<script setup lang="ts">
import { reactive, readonly, ref } from 'vue'
const sum = ref(0)
// sum的值改变,sum2的值会跟着改变
let sum2 = readonly(sum) // 这里sum2是和sum绑定的,所以sum的值改变,sum2也会跟着改变
function changeSum() {
sum.value++
}
function changeSum2() {
// sum2.value++ 这句会报错,sum2是只读的,不能修改
}
// reactive也可以实现只读
let car = reactive({
name: '奔驰',
options: {
price: 100,
color: '黑色'
}
})
let car2 = readonly(car) // car2和car是绑定的,所以car的值改变,car2也会跟着改变, 但是car2中的内容是不能修改的
</script>
shallowReadonly是浅只读,只能修改响应式对象第一层的属性
vue
<script setup lang="ts">
import { reactive, readonly, ref, shallowReadonly } from 'vue'
// reactive也可以实现只读
let car = reactive({
name: '奔驰',
options: {
price: 100,
color: '黑色'
}
})
let car2 = shallowReadonly(car) // car2是浅只读,只能修改car2的属性,不能修改嵌套属性的值
</script>
toRaw & markRaw
toRaw根据响应式对象生成一个原始对象
vue
<template>
<div>
<h1>person:{{person}}</h1>
<button @click="changeAge">age+1</button>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, toRaw } from 'vue'
let person = reactive({
name: '奔驰',
age: 18
})
// 获取响应式对象的原始对象,也就是非响应式对象,仅支持展示,不能修改原始对象的数据结构。
let person2 = toRaw(person)
function changeAge() {
person.age++
}
</script>
markRaw 标记一个对象,使其永远不能成为响应式的
vue
<template>
<div>
<h1>car{{car}}</h1>
<button @click="changeCar">age+1</button>
</div>
</template>
<script setup lang="ts">
import { markRaw } from 'vue';
// 标记为原始对象,不会被代理
let car = markRaw({
name: 'benz',
age: 10,
})
// 修改原始对象,不会触发视图更新
function changeCar() {
car.age += 1;
}
</script>
customRef
场景:使用ref定义的数据修改之后会立即修改界面展示,如果我们想改了数据之后延迟2s再修改界面,就需要customRef
customRef一般会封装为hooks,创建
src/hooks/useMsgRef.ts
typescript
import { customRef } from 'vue';
// 接收初始值initval 和 延迟时间 delay,都有默认值
export default function (initval: number = 0, delay: number = 1000) {
let timer;
let msg = customRef((track, trigger) => {
return {
get() {
// track告诉vue数据很重要,一旦数据变化就要去更新
track()
return initval;
},
set(newValue) {
// 每次设置都清楚上次的timer,避免set的时候开启很多timer
clearTimeout(timer);
timer = setTimeout(() => {
console.log('newValue', newValue);
initval = newValue;
// 通知vue数据变化了
trigger()
}, delay);
}
}
})
return { msg }
}
使用者
vue
<template>
<div>
<h1>{{msg}}</h1>
<!-- 通过输入框修改msg值 -->
<input type="text" v-model="msg">
</div>
</template>
<script setup lang="ts">
import useMsgRef from '@/hooks/useMsgRef';
const msgRef = useMsgRef(1, 2000)
const {msg} = msgRef;
</script>
Teleport
一种能将我们组件html结构移动到指定位置的技术
示例:一个弹窗展示到视图中央
App.vue
vue
<script setup>
import Modal from './Modal.vue';
</script>
<template>
<div class="outer">
<h1>我是app组件</h1>
<img src="https://ts1.tc.mm.bing.net/th/id/OIP-C.OFMlQdJWRGUjT2PNEWN00AHaEK?rs=1&pid=ImgDetMain&o=7&rm=3" alt="">
<br>
<Modal />
</div>
</template>
<style scoped>
.outer {
background-color: #ddd;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 5px;
width: 400px;
height: 400px;
/* 这句filter会导致 Modal组件的postion:fixed失效,因此使用teleport解决 */
filter: saturate(0%);
}
img {
width: 180px;
}
</style>
Modal.vue
vue
<template>
<button @click="isShow=true">展示弹窗</button>
<div class="modal" v-show="isShow">
<h1>我是弹窗标题</h1>
<p>我是弹窗内容</p>
<button @click="isShow=false">关闭</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
let isShow = ref(false)
</script>
<style lang="scss" scoped>
.modal {
width: 200px;
height: 170px;
background-color: skyblue;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
text-align: center;
/* 相对居中,但是父容器的filter会导致失效 */
position: fixed;
left: 50%;
top: 20px;
margin-left: -100px;
}
</style>
使用teleport解决弹窗位置问题
vue
<!-- to代表将代码瞬移到哪个标签下 -->
<teleport to='body'>
<div class="modal" v-show="isShow">
<h1>我是弹窗标题</h1>
<p>我是弹窗内容</p>
<button @click="isShow=false">关闭</button>
</div>
</teleport>
Suspense
等待异步组件时渲染一些额外内容,提高用户体验
使用场景:子组件setup里包含异步任务,异步任务返回的内容还要展示
示例先安装网络请求库npm i axios
,使用网络请求作为异步
Child.vue
vue
<script setup>
import axios from 'axios';
const {data:{content}} = await axios.get('/api/rand.qinghua')
console.log(content);
</script>
<template>
<div class="child">
<h1>我是child组件</h1>
<div>{{content}}</div>
</div>
</template>
<style scoped>
.child {
background-color: skyblue;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>
App.vue
vue
<script setup>
import Child from './Child.vue'
</script>
<template>
<div class="app">
<h1>我是app组件</h1>
<Suspense>
<template v-slot:default>
<Child></Child>
</template>
<template v-slot:fallback>
<h1>加载中...</h1>
</template>
</Suspense>
</div>
</template>
<style scoped>
.app {
background-color: #ddd;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>
全局api转移到应用对象
通过
app.component
注册全局组件,在任何地方都可以直接使用,不需要再import
tsx
const app = createApp(App)
app.component('Hello', Hello)
通过
app.config.globalProperties.x
设置全局属性
typescript
app.config.globalProperties.x = 99
使用的时候ts会报类型找不到,在main.ts文件中(不是js文件)添加自定义属性类型即可结论类型问题
typescript
declare module 'vue' {
interface ComponentCustomProperties {
x: number
}
}
注册全局指令
typescript
注册
app.directive('beauty', (element, {value})=>{
element.innerText += ` --- ${value*2}`
element.style.color = "yellow"
element.style.backgroundColor = "green"
})
app.mount('#app')
使用:
<div class="child">
<h1>我是Hello组件</h1>
<div v-beauty="x">app.config.globalProperties.x : {{x}}</div>
</div>
其他
typescript
app.mount('#app')
app.unmount()
app.use(router)