Vue3学习笔记

创建Vue3工程

基于vite进行工程的创建。官方链接

1
npm create vue@latest

Vue3核心语法

选项式API和组合式API

vue2:选项式API

vue3:组合式API

选项式API中,数据、方法、计算属性等等是分散开的,若想添加需求,需要到不同的选项中修改内容,不便于维护和复用。

setup

setup函数中的thisundefined
在setup中直接声明变量不是响应式的。

setup的执行在beforeCreate之前。

setup的返回值也可以是一个渲染函数。

1
2
3
setup(){
return ()=>'666' //此时模板内容无效,页面上只会有“666”
}

面试题:setup和传统的配置项(data,methods)能不能同时写?

可以同时写,data可以读到setup中的数据。

1
2
3
4
5
6
7
8
9
10
data(){
return {
name:this.name // 1111
}
}

setup(){
const name = `1111`
return {name}
}

语法糖:

1
2
3
4
5
6
7
8
9
10
11
<script setup>
const a = 666
</script>
<!-- 以上写法等价于>>>>>> -->
<script>
setup()
{
const a = 666
return {a}
}
</script>

如何在只想写一个setup的情况下还实现对组件的命名呢?

可以使用插件来实现。

  1. 安装
1
npm i vite-plugin-vue-setup-extend -D
  1. 配置vite.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
import VueSetupExtend from 'vite-plugin-vue-setup-extend'       // 添加
export default defineConfig({
plugins: [
vue(),
VueSetupExtend() // 添加
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

  1. 使用
1
2
<script setup name='test'>
<script>

ref和reactive

ref用来定义基本类型数据、对象类型数据。

reactive用来定义对象类型数据。

  • 区别

ref创建的对象必须试用.value(可以使用volar插件自动添加)

reactive在重新分配对象的时候会失去响应式。(可以使用Object.assign进行整体替换)

1
2
3
4
5
6
7
8
9
10
11
12
const student = reactive({ name: '小玉', age: 20 })
const teacher = ref({ major: 'math', sex: 'male' })
function changeSexTest() {
// 对于reactive
// student = { name: '小玉', age: 20 } // 失去响应
// student = reactive({ name: '小玉', age: 20 }) // 失去响应
Object.assign(student, { name: '小玉', age: 18 })

// 对于ref
// teacher = ref({ major: 'math', sex: 'male' }) // 失去响应
teacher.value = {major: 'math', sex: 'female'} // ok
}
  • 使用原则

对于基本类型的响应式数据,使用ref

层级不深的响应式数据,两者都可以

层级较深的响应式数据,推荐使用reactive

torefs和toref

1
2
3
4
5
6
7
8
9
10
// torefs 和 toref

// 对响应式数据进行解构的时候会失去响应
// const { name, age } = student // 失去响应

// toRefs
import { toRefs,toRef } from 'vue'
let { name, age } = toRefs(student) // ok
// toRef
let name2 = toRef(student, 'name') // ok

计算属性

计算属性有缓存,多次使用只会调用一次。(数据未改变时)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ref, computed } from 'vue'
const firstName = ref('小')
const lastName = ref('猪')

// 这种写法 fullName只读的
const fullName = computed(() => {
return firstName.value + '-' + lastName.value
})

// 这种写法 WFullName 可读可写 WFullName.value = 'xxxx' 会调用
const WFullName = computed({
set(val: String) {
const [first, last] = val.split('-')
firstName.value = first
lastName.value = last
},
get() {
return firstName.value + '-' + lastName.value
}
})

watch

watch只能监视以下四种数据:

ref定义的数据;

reactive定义的数据;

函数返回一个值;

一个包含上述内容的数组。

  • 情况1,监视ref类型的基本类型数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { ref, watch } from 'vue'
const num = ref(0)

function add() {
num.value += 1
}
// 情况1 监视ref定义的数据
const stop = watch(num, (newValue, oldValue) => {
console.log(`数据发生变化了~`, newValue, oldValue);
// 超过10以后停止监控
if (newValue > 10) {
stop()
}
})
  • 情况2,监视ref定义的对象类型的数据。
  • 若修改的是对象中的属性,newValueoldValue都是旧值,因为他们都是同一个对象。

  • 若修改的是整个ref定义的对象,newValue是新值,oldValue是旧值,不是同一个对象。

1
2
3
4
5
6
7
8
9
// 情况2 监视ref定义的对象类型数据

const user = ref({ name: '小明', age: 18 })
function changeUserName() {
user.value.name = '小黄'
}
watch(user, (newValue, oldValue) => {
console.log(`user被修改了`, newValue, oldValue);
}, { deep: true })
  • 情况3,监视reactive定义的对象类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 情况3 监视reactive定义的对象类型数据
import { reactive } from 'vue'
const person = reactive({ name: '张三', age: 20 })
function changePersonName() {
person.name = `范小勤`
}
function changePerson() {
Object.assign(person, { name: '开发5G', age: 14 })
}
// 默认开启deep监听,且无法关闭 newValue和oldValue都是同一个值
watch(person, (newValue, oldValue) => {
console.log(`person被修改了~`, newValue, oldValue);
})
  • 情况4,监视ref或者reactive定义的数据类型数据中的某个属性,注意:
  • 如果该属性不是对象类型,需要写成函数形式。getter

  • 如果该属性依然是对象类型,可以直接编写,但建议写成函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script setup>
import { ref, reactive, watch } from 'vue'

const person = reactive(
{
name: '张三',
age: 20,
address: {
city: '北京'
province: '北京'
}
})

// 只监视person.name属性的变化
watch(() => person.name, () => {
console.log('name属性被修改了')
})

// 监视person.address属性的变化 这个时候监视的是地址值,整个address变化的时候才会触发
watch(() => person.address, () => {
console.log('address属性被修改了')
})
// 监视person.address.city属性的变化 这样写,某个属性变化的时候才会触发,整个address变化不会触发
watch(person.address, () => {
console.log('city属性被修改了')
})

// 以上两种情况都可以触发
watch(() => person.address, () => {
console.log('address属性被修改了')
}, { deep: true })

</script>
  • 监视多个值。
1
2
3
4
// 监视多个属性
watch([() => person.name, () => person.age,person.address], () => {
console.log('name或age属性被修改了')
})

watchEffect

1
2
3
4
5
6
7
8
9
10
import { ref, watchEffect } from 'vue'
const a = ref(0)
const b = ref(0)

// 使用 watchEffect自动监听 第一次会自动执行
watchEffect(() => {
if (a.value == 1 && b.value == 1) {
console.log('a==b==1')
}
})

标签的ref属性

用于绑定DOM元素,如果绑定的是组件,则获得的是组件实例。父组件可以获取子组件通过defineExpose导出的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<div ref="content">我是div</div>
<button @click="show">show</button>
</div>
</template>

<script setup>
import { ref } from 'vue'
const content = ref()
function show() {
console.log(content.value);
}
</script>

TS接口和泛型

types/index.ts

1
2
3
4
5
export interface Person {
name: string,
age: number
}
export type PersonList = Array<Person>

demo.vue

1
2
3
4
5
6
7
8
9
10
import { type Person,type PersonList } from "@/types";

const bag: Person = {
name: 'bag',
age: 18
}

const girls: PersonList = [
{ name: 'bag', age: 18 }
]

Props

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<propsdemo :list="persons" />
</template>

<script setup lang="ts">
import {type PersonList } from "@/types";
import propsdemo from "./propsdemo.vue";

import { reactive } from 'vue'
const persons = reactive<PersonList>([
{ name: 'bag', age: 18 }
])
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { defineProps, withDefaults } from 'vue'
import { type PersonList } from '@/types'
// 只接受list
// defineProps(['list'])

// 接受list + 限制类型
defineProps<{ list: PersonList }>()

// 接受list + 限制类型 + 限制必要性 + 指定默认值
withDefaults(defineProps<{ list?: PersonList }>(), {
// 以函数的形式返回值
list: () => [
{ name: 'bag', age: 18 }
]
})
// 接受list并保存到 props
const props = defineProps(['list'])

生命周期

  • vue2

    生命周期hook
    创建前;创建完毕beforeCreate();created()
    挂载前;挂载完毕beforeMount();Mounted()
    更新前;更新完毕beforeUpdate();updated()
    销毁前;销毁完毕beforeDestory();destroyed()
  • vue3

    setup的执行在beforeCreate之前。

    生命周期hook
    挂载前;挂载完毕onBeforeMount(), onMounted()
    更新前;更新完毕onBeforeUpdate(),onUpdated()
    卸载前;卸载完毕onBeforeUnmount(),onUnmounted()

父子组件中,子挂载在父挂载之前完成。因此App组件是最后挂载的。

自定义hooks

useSum.ts

1
2
3
4
5
6
7
8
9
10
11
import { ref, type Ref } from 'vue'

export default function () {
const sum: Ref = ref<number>(0)

function add() {
sum.value += 1
}

return {sum, add}
}

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
{{ sum }}
</div>
<button @click="add">add</button>
</template>

<script setup lang="ts">
import useSum from '@/hooks/useSum'

const { sum, add } = useSum()

</script>

路由

基本使用

src/router/index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createRouter, createWebHistory } from 'vue-router'
import Person from '@/components/lifecycle.vue'
const router = createRouter({
routes: [
{
path: '/home',
component: () => Person
}
],
history: createWebHistory()
})

export default router

main.ts

1
2
3
4
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router'
createApp(App).use(router).mount('#app')

在需要切换的区域:
active-class="activate-nav"指定激活时的css。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<nav class="nav">
<RouterLink to="/" active-class="activate-nav">首页</RouterLink>
<RouterLink to="/about" active-class="activate-nav">关于</RouterLink>
</nav>
<RouterView />
</div>
</template>

<script setup>
import { RouterView } from 'vue-router'
</script>

组件切换时,视觉上消失的组件实际上是被卸载掉了。

工作模式

  • history
    • 优点:URL美观,路径不带#,接近传统的网站URL。
    • 缺点:项目上线需要后端配合处理路径问题。否则主页面之外的其他页面刷新会报404
  • hash
    • 优点:兼容性好,不需要服务器处理路径。
    • 缺点:URL不美观,SEO优化方面较差。

To C常用history模式,后台管理常用hash模式。

to的两种写法

  • 写法一
    <RouterLink to="/">首页</RouterLink>
  • 写法二
    <RouterLink :to="{path:'/home'}">首页</RouterLink>

命名路由

顾名思义就是给路由取一个名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const router = createRouter({
history: createWebHistory(),
routes: [
{
name:"zhuye",
path: '/',
component: Home,
},
{
name:"guanyu",
path: '/about',
component: About,
}
]
})

配置名字以后,在进行路由跳转的时候可以通过名字进行。

<RouterLink :to="{name:'zhuye'}">首页</RouterLink>

嵌套路由

使用children配置项配置子路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const router = createRouter({
history: createWebHistory(),
routes: [
{
name: "zhuye",
path: '/home',
component: Home,
children: [
{
path: 'content',
component: content
}
]
}
]
})

Home页面响应位置使用<RouterView />标签。

参数传递

  • query

    参数在URL里面,形如:/home/news?id=10

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <h1>首页页面</h1>
    <h3>二级导航</h3>
    <nav>
    <span v-for="item in news" :key="item.id">
    <RouterLink
    :to="`/home/content?content=${item.content}`">
    {{ item.name }}
    </RouterLink>
    </span>
    </nav>
    <RouterView />
    </template>

    参数项太多会很恶心,可以换一个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     <span v-for="item in news" :key="item.id">
    <RouterLink
    :to="{
    path:'/home/content',
    query:{
    id:item.id,
    content:item.content,
    name:item.name
    }
    }">
    {{ item.name }}
    </RouterLink>
    </span>

    读取参数。

    1
    2
    3
    4
    5
    6
    7
    <template>
    <h3>资讯内容:{{ route.query.content }}</h3>
    </template>
    <script setup>
    import { useRoute } from "vue-router"
    const route = useRoute()
    </script>
  • params

    配置路由。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const router = createRouter({
    history: createWebHistory(),
    routes: [
    {
    name: "zhuye",
    path: '/home',
    component: Home,
    children: [
    {
    path: 'content/:id/:content',
    component: content
    }
    ]
    }
    ]
    })

    传递参数。

    1
    2
    3
    4
    5
    <RouterLink 
    class="navItem"
    :to="`/home/content/${item.id}/${item.content}`">
    {{ item.name }}
    </RouterLink>

    另一种写法,此处必须使用name参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <RouterLink class="navItem" 
    :to="{
    name: 'xiangqing',
    params:{
    id:item.id,
    content:item.content
    }

    }">
    {{ item.name }}
    </RouterLink>

    获取参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <template>
    <h3>资讯内容:{{ params.id + "," + params.content }}</h3>
    </template>
    <script setup>
    import { useRoute } from "vue-router"
    import { toRefs, toRef } from "vue"
    const route = useRoute()
    const { params } = toRefs(route)
    </script>

    路由规则props配置

    props设置为true时,route.params将被设置为组件的props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const router = createRouter({
history: createWebHistory(),
routes: [
{
name: "zhuye",
path: '/home',
component: Home,
children: [
{
name:'xiangqing',
path: 'content/:id/:content',
component: content,
props:true // 自动将id,content设置为content组件的props
}
]
}
]
})

props写成一般对象时,等同于给组件设置props。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const router = createRouter({
history: createWebHistory(),
routes: [
{
name: "zhuye",
path: '/home',
component: Home,
children: [
{
name:'xiangqing',
path: 'content',
component: content,
props:{ //等同于 <content a=1 b=2 c=3 />
a:1,
b:2,
c:3
}
}
]
}
]
})

props写成函数时,函数的参数为route,可以返回props的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const router = createRouter({
history: createWebHistory(),
routes: [
{
name: "zhuye",
path: '/home',
component: Home,
children: [
{
name:'xiangqing',
path: 'content',
component: content,
props:route=>(route.query) // 用于query传参
}
]
}
]
})

组件内声明props即可。

1
2
3
4
5
6
<template>
<h3>资讯内容:{{ id + "," + content }}</h3>
</template>
<script setup>
defineProps(['id', 'content'])
</script>

replace属性

在history模式下,浏览器的访问记录会以栈的形式维护。导航到新的页面后,可以通过后退返回到上个记录。

使用replace会在当前栈进行原地替换。

1
<RouterLink replace to="/home">首页</RouterLink>

编程式路由导航

1
2
3
4
5
6
7
8
9
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function GotoNews() {
router.push({ // 等同于<RouterLink /> 标签中的`to`属性。
path: '/home/content/1/干净又卫生,干了兄弟们!'
})
}
</script>

重定向

1
2
3
4
5
6
7
8
9
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
redirect: '/home'
}
]
})

状态管理

组件间共享数据。

pinia的安装和配置。

1
2
3
yarn add pinia
# 或者使用 npm
npm install pinia
1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

定义。

1
2
3
4
5
6
7
8
9
10
11
12
import { defineStore } from 'pinia'
const useCounterStore = defineStore('count', {
state: () => {
return {
count: 0
}
}
})

export {
useCounterStore
};

使用。

1
2
3
4
5
6
7
8
9
10
<template>
<div>
{{ CounterStore.count }}
</div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/store/count'
const CounterStore = useCounterStore()
</script>

修改。
第一种方法:直接修改。

1
2
3
function add() {
CounterStore.count++
}

第二种方法:patch。适合一次修改多个值。

1
2
3
4
5
function add() {
CounterStore.$patch({
count: 100
})
}

第三种方法:action。在store中定义,可以在修改值之前添加约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const useCounterStore = defineStore('count', {
state: () => {
return {
count: 0
}
},
actions: {
add(val: number) {
if (this.count < 10) {
this.count += val
}
}
}
})

function add() {
CounterStore.add(1)
}

storeToRefs

直接结构出来store的值,会失去响应性,如下。

1
2
3
4
5
6
7
8
9
10
<template>
<div>
{{ }}
</div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/store/count'
const {count} = useCounterStore()
</script>

理论上可以通过toRefs进行包裹,但会解构出来许多不需要的属性,因此推荐使用storeToRefs

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
{{ count }}
</div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/store/count'
import { storeToRefs } from "pinia";
const CounterStore = useCounterStore()
const { count } = storeToRefs(CounterStore)
</script>

getter函数

相当于共享的computed。两种写法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const useCounterStore = defineStore('count', {
state: () => {
return {
count: 0
}
},
actions: {
add(val: number) {
if (this.count < 10) {
this.count += val
}
}
},
getters: {
// 写法1
mulCount: state => state.count * 10,
// 写法2
TenCount(): number {
return this.count * 10
}
}
})

subscribe

类似于watch,数据发生改变的时候被调用。

1
2
3
4
5
6
7
<script setup lang="ts">
import { useCounterStore } from '@/store/count'
const CounterStore = useCounterStore()
CounterStore.$subscribe((mutation, state) => {
console.log('@@@@@');
})
</script>

组合式Store

1
2
3
4
5
6
7
8
9
10
11
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useSumStore = defineStore('sum', () => {
const sum = ref(0)
function getSum() {
return sum.value * 10
}
return { sum, getSum }
})

export { useSumStore }

组件通信

父子组件通信

父组件->子组件。

父组件。

1
2
3
4
5
6
7
8
<template>
<comp1 :game="game" />
</template>
<script setup>
import comp1 from '@/components/comp1.vue';
import { ref } from "vue"
const game = ref('LOL')
</script>

子组件。

1
2
3
4
5
6
7
8
<template>
<div>
{{ game }}
</div>
</template>
<script setup lang="ts">
defineProps(['game'])
</script>

子组件->父组件。

父组件。

1
2
3
4
5
6
7
8
9
10
11
<template>
<comp1 :sendGame="getGame" />
</template>
<script setup>
import comp1 from '@/components/comp1.vue';
import { ref } from "vue"
const game = ref('')
function getGame(value){
game.value = value
}
</script>

子组件。

1
2
3
4
5
6
7
8
<template>
<div>
<button @click="sendGame('LOL')">发送</button>
</div>
</template>
<script setup lang="ts">
defineProps(['sendGame'])
</script>

自定义事件

父组件。

1
2
3
4
5
6
7
8
<template>
<comp2 @get-value="GetValue" />
</template>
<script setup>
function GetValue(value) {
console.log(value);
}
</script>

子组件。

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
import { onMounted } from 'vue'
const emit = defineEmits(['get-value'])
// 假定希望3s后执行
onMounted(() => {
setTimeout(() => {
emit('get-value', 6)
}, 3000)
})
</script>

mitt

https://github.com/developit/mitt

实现任意组件间的通信,以下使用兄弟组件进行演示。com1传递数据给com2

安装mitt

1
npm install --save mitt

新建utils/mitt.ts

1
2
3
4
5
import mitt from 'mitt'

const emitter = mitt()

export default emitter

com2

1
2
3
4
5
6
7
8
9
10
11
12
13
import emitter from '@/utils/mitt';

onMounted(() => {
// 绑定事件
emitter.on('GetData', (value) => {
console.log(value);
})
})

onUnmounted(() => {
// 解绑事件
emitter.off('GetData')
})

com1 点击按钮后会将6作为参数传递到com2中。

1
2
3
4
5
6
7
8
9
<template>
<div>
<button @click="emitter.emit('GetData', 6)">传送数据</button>
</div>
</template>

<script setup lang="ts">
import emitter from '@/utils/mitt';
</script>

V-model

https://cn.vuejs.org/guide/components/v-model.html

实现父子组件的双向绑定。

父组件。

1
2
3
4
5
6
7
<template>
<comp1 v-model="inputV" />
</template>
<script setup>
import comp1 from '@/components/comp1.vue';
import { ref } from "vue"
const inputV = ref()

子组件。

1
2
3
4
5
6
7
8
9
<template>
<div>
<input type="text" v-model="inputV" />
</div>
</template>

<script setup lang="ts">
const inputV = defineModel()
</script>

attrs

父组件通过v-bind传给子组件的值或方法,子组件若未defineProps,可以通过$attrs进行引用。
可以利用attrs完成祖组件和孙组件之间的数据传递。

如:grandfather.vue

1
<father v-bind="{x:100,y:200}" />

father.vue

1
<son v-bind="$attrs" />

son.vue

1
defineProps(['x','y'])

$refs

https://cn.vuejs.org/guide/essentials/template-refs.html

通过在子组件上定义ref可以获取子组件的实例对象,进而得到子组件通过defineExpose导出的数据。

1
2
3
<child ref="child" />

const child = ref()

$refs可以获取所有子组件的实例。

1
<button @click="getAllChild($refs)">获取所有子组件</button>

$parent

当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为null

1
<button @click="getFather($parent)">获取所有子组件</button>

额外的解包细节

https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#additional-ref-unwrapping-details

provide & inject

provide 和 inject 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。

祖先组件。

1
2
3
4
5
6
7
8
9
<script setup>
import { ref,provide } from "vue"
const game = ref('LOL')
function GetGame(game) {
console.log(game);
}
// 向后代提供数据
provide('data',{game,GetGame})
</script>

子孙组件。
1
2
import { inject } from 'vue'
const { game, GetGame } = inject('data',{game:'',GetGame:()=>{}})

pinia

slot

  • 默认插槽

子组件。

1
2
3
4
5
6
<template>
<div class="root">
<h1>Component 2</h1>
<slot>默认内容</slot>
</div>
</template>

父组件。

1
2
3
4
 <comp2>
<img src="xxx.jpg" alt="">
<!-- 可以填充更多内容 -->
</comp2>
  • 具名插槽

顾名思义是具有名字的插槽。

子组件。

1
2
3
4
5
6
7
<template>
<div class="root">
<h1>Component 2</h1>
<slot name="s1">默认内容1</slot>
<slot name="s2">默认内容2</slot>
</div>
</template>

父组件。

1
2
3
4
5
6
7
8
9
10
11
 <comp2>
<template v-slot:s2>
<img src="xxx.jpg" alt="">
</template>

<template #s1>
<img src="xxx.jpg" alt="">
</template>

<!-- v-slot && # 是两种不同的写法 -->
</comp2>
  • 作用域插槽。

场景:数据由子组件来维护,但子组件内部的结构是由父组件来决定的。
例如:子组件内有一组数据,展示方式由父组件来决定。

子组件。

1
2
3
<template>
<slot :data = "games" :title="title"></slot>
</template>

父组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
...
<!-- params是子组件传递给父组件的所有的参数。 -->
<!-- params:{data:{...},title:"xxx"} -->
<template v-slot="params">
<ul>
<li v-for="item in params.games">{{item.game}}</li>
</ul>
<!-- 解构写法 -->
</template v-slot="{title}">
<h3>{{title}}</h3>
</template>
...
</template>

具名 + 作用域 插槽。

子组件。

1
2
3
<template>
<slot :data = "games" :title="title" name="s1"></slot>
</template>

父组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
...
<!-- params是子组件传递给父组件的所有的参数。 -->
<!-- params:{data:{...},title:"xxx"} -->
<template v-slot:s1="params">
<ul>
<li v-for="item in params.games">{{item.game}}</li>
</ul>
<!-- 解构写法 -->
</template v-slot="{title}">
<h3>{{title}}</h3>
</template>
...
</template>

其他API

shallowRef & shallowReactive

  • shallowRef:ref的浅层作用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <script setup lang="ts">
    import {shallowRef} from 'vue'
    const data1 = shallowRef(0)

    function changeData1(){
    data1.value = 1 // 有响应性
    }

    const data2 = shallowRef({
    name:'小红花',
    age:18
    })
    function changeData2Name(){
    data2.value.name = '小绿植' // 没有响应性
    }

    function changeData2(){ // 有响应性
    data2.value = {
    name:'小绿植',
    age:22
    }
    }
    </script>
  • shallowReactive:reactive的浅层作用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { shallowReactive } from 'vue'
    const data3 = shallowReactive({
    name: '落叶',
    age: 18,
    student:{
    name:'bobo',
    age:20
    }
    })

    function changeData3() {
    data3.name = '芒果' // 有响应性
    data3.student.name = 'yuan' // 这种情况下还是会更新
    // 原因:https://github.com/vuejs/core/issues/5740
    }
    function changeData3name() {
    data3.student.name = 'yuan' // 没有响应性
    }

    readonly && shallowReadonly

  • readonly:深层次的只读。
  • shallowReadonly:浅层次的只读。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { ref,readonly,shallowReadonly } from 'vue'

const data1 = ref(0)
const data2 = readonly(data1) // 这种关系会维持,即data1变,data2跟着变。

function changeData(){
data2.value = 1 // 无法修改
}

const data3 = reactive(
{
name:'小红花',
age:18,
teacher:{
name:'落叶',
age:30
}

})

const data4 = shallowReadonly(data3) // 关系维持

function changeData4(){
data4.name = '芒果' // 无法修改
data4.teacher.name = '葡萄' // 可以修改
}

toRaw && markRaw

  • toRaw:根据一个Vue创建的代理返回其原始对象。

使用场景:将响应式数据传递给外部系统使用的时候,可以确保外部系统收到的是原始值。

1
2
3
4
5
6
7
import {reactive,toRaw} from 'vue'
const data = reactive({
name:'小红花',
age:18
})

const data2 = toRaw(data)
  • markRaw:将一个对象标记为不可被转为代理。返回该对象本身。
    1
    2
    3
    4
    5
    6
    7
    import {reactive,markRaw} from 'vue'
    const data = markRaw({
    name:'小红花',
    age:18
    })

    const data2 = reactive(data) // 没有响应性

    customRef

why? 自带的ref的功能就是:数据发生改变,然后更新视图。

如果我想在数据发生改变后,做一些操作呢?比如延迟1s后执行更新。

使用自定义ref实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { customRef } from 'vue'

export function DalayRef(initValue:string,timer:number = 1000) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 告诉vue应该重点关注initValue
return initValue;
},
set(value) {
timeout = setTimeout(() => {
initValue = value;
trigger() // 告诉vue数据发生变化了
}, timer)
},
}
})
}

使用。

1
2
3
4
5
6
7
8
9
10
11
12
<template>
{{ msg }}
<input v-model="msg" />
</template>

<script setup lang="ts">

import { DalayRef } from '@/utils/useDelayRef'

const msg = DalayRef('666',5000)

</script>

Teleport

<Teleport>是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

例如:

子组件是一个模态框,在父组件中使用。

子组件的CSSposition: fixed能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了transformperspective或者filter样式属性。

如果父组件使用了以上样式属性,模态框的位置会出问题,因此,处理方式是将子组件转移到其他DOM结构,可以使用来实现。

1
2
3
4
5
6
7
8
9
<template>
...
<!-- 要转移的内容 -->
<!-- to="#app" 选择器也可以-->
<Teleport to="body">
.....
</Teleport>

</template>

Suspense

https://cn.vuejs.org/guide/built-ins/suspense.html
试验性内容。

全局API转移到应用对象

https://cn.vuejs.org/api/application.html

  • app.component()

  • app.config.globalProperties

  • app.directive()

非兼容性改变

https://v3-migration.vuejs.org/zh/breaking-changes/