Vue3笔记

一、创建Vue3工程

1
2
3
4
npm create vite@latest vue3-project --template vue-ts
cd vue3-project
npm install
npm run dev

二、编写App组件

./src/main.ts

1
2
3
import {createApp} from "vue";  
import App from "./App.vue";
createApp(App).mount("#app");

./src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 写js或ts代码
<script lang="ts">
export default {
name: 'App'
}
</script>
// 写web的html框架
<template>
<div class="app">
<p>hahaha!</p>
</div></template>
// 写css样式
<style scoped>
.app {
background-color: gray;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>

三、核心语法

1. setup()

1. 概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script lang="ts">  
import {defineComponent} from "vue";
export default defineComponent({
name: 'App',
setup(){
let name = "xxx";
let age = 18;
let tel = '13888888888';
function changeName() {
name = 'xx';
}
function changeAge() {
age += 1;
}
function showTel() {
alert(tel);
}
return {name, age, tel, changeName, changeAge, showTel}
}
})
</script>

Vue3已经开始弱化this了
此时的name和age不是响应式的

2. 返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script lang="ts">  
import {defineComponent} from "vue";
export default defineComponent({
name: 'App',
setup(){
let name = "xxx";
let age = 18;
let tel = '13888888888';
function changeName() {
name = 'xx';
}
function changeAge() {
age += 1;
}
function showTel() {
alert(tel);
}
return () => 'haha'
}
})
</script>

setup()返回值是一个渲染函数时,返回内容会直接渲染到页面。

3. Setup()与OptionsAPI

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
<script lang="ts">  
import {defineComponent} from "vue";
export default defineComponent({
name: 'App',
data() {
a: 100,
c: this.name,// 可以读到setup中的name
d: 900
},
method: {
b() {
console.log('b');
}
},
setup() {
let name = "xxx";
let age = 18;
let tel = '13888888888';
let e = d; // 报错,setup()读不到data的数据
function changeName() {
name = 'xx';
}
function changeAge() {
age += 1;
}
function showTel() {
alert(tel);
}
return {name, age, tel, changeName, changeAge, showTel}
}
})
</script>

4. 语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script lang="ts">  
import {defineComponent} from "vue";
export default defineComponent({
name: 'App'
})
</script>
<script setup lang="ts">
let name = "xxx";
let age = 18;
let tel = '13888888888';
function changeName() {
name = 'xx';
}
function changeAge() {
age += 1;
}
function showTel() {
alert(tel);
}
</script>

<script>setup属性可以省略setup()return部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
defineOptions({ name: 'App' });
let name = "xxx";
let age = 18;
let tel = '13888888888';
function changeName() {
name = 'xx';
}
function changeAge() {
age += 1;
}
function showTel() {
alert(tel);
}
</script>

可以用宏定义defineOptions({})来设置组件名称

2. 响应式

1. ref定义基本类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {ref} from 'vue';
defineOptions({ name: 'App' });
let name = ref("xxx"); // 加ref()让数据变成对象
let age = ref(18);
let tel = '13888888888';
function changeName() {
name.value = 'xx'; // 对value值进行修改
}
function changeAge() {
age.value += 1;
}
function showTel() {
alert(tel);
}
</script>
<template>
<div class="app">
<p>{{name}}</p> // vue自动解包,不需要.value
</div>
</template>

2. reactive定义对象类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts"> 
import {reactive} from 'vue';
defineOptions({ name: 'Car' });
let car = reactive({brand: '奔驰', price: 100});
function changePrice() {
car.price += 10;
}
</script>
<template>
<div class="car">
<h2>一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changePrice">修改汽车价格</button>
</div>
</template>

reactive只能定义对象类型响应数据

3. ref定义对象类型的数据

4. ref对比reactive

1. 宏观角度

  • ref用来定义:基本类型数据,对象类型数据
  • reactive用来定义:对象类型数据

2. 区别

  • ref创建变量必须使用.value(可以用volar插件自动加.value
  • reactive重新分配一个新对象,会失去响应式(可以用Object.assign去整体替换)。

3.使用原则

  1. 若需要一个基本类型的响应式数据,必须用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以
  3. 若需要一个响应式对象,且层级较深,建议使用reactive

5.toRefstoRef

新版本直接解构即可,不再会丢失响应性

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts"> 
import {reactive, toRefs, toRef} from 'vue';
let person = reactive({
name: "xxx",
age: 18
})
let {name, age} = toRefs(person) ;
let n1 = toRef(person,'age');
</script>
<template>
<div class="person">
</div>
</template>

使用toRefs对响应式对象person进行解构赋值,确保解构后的nameage仍保持响应性
解构后的属性需通过.value访问,与原始响应式对象属性双向绑定

3. computed()

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
<script setup lang="ts"> 
import {ref, computed} from 'vue';

let lastName = ref('张');
let firstName = ref('三');
let fullName = computed(() => {
return lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1) + '-' + firstName.value;
})
/*
let fullName = computed({
get() {
return lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1) + '-' + firstName.value;
},
set(val) {
const [str1, str2] = val.split('-');
lastName.value = str1;
firstName.value = str2;
}
})
*/
</script>
<template>
<div class="person">
姓:<input type="text" v-model="lastName"> <br>
名:<input type="text" v-model="firstName"> <br>
全名: <span>{{lastName}}-{{firstName}}</span> <br>
</div>
</template>

computed会缓存计算结果,只有当其依赖的响应式数据(如refreactive变量)发生变化时才会重新计算。
computed是只读的,使用注释的方法可以实现可读写

4. watch()

  • 作用:监视数据的变化
  • 特点:Vue3中的watch只能监视以下四种数据
    1. ref定义的数据
    2. reactive定义的数据
    3. 函数返回一个值(getter函数)
    4. 一个包含上述的数组

情况一

watch监视ref定义的基本类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts"> 
import {ref, watch} from 'vue';

let sum = ref(0);

function changeSum() {
sum.value +=1;
}

const stopWatch = watch(sum, (newValue, oldValue) => {
console.log('sum is Changed', newValue, oldValue)
if(newValue > 10) {
stopWatch();
}
})
</script>
<template>
<div>
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">sum+1</button>
</div>
</template>

watch()函数会返回一个​​停止监听的函数,调用该函数可以停止监视
第二个参数中的newValueoldValue指向的是地址

情况二

watch监视ref定义的对象类型数据

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
<script setup lang="ts"> 
import {ref, watch} from 'vue';

let person = {
name: 'xxx',
age: 18
}

function changeName() {
person.value.name += '~';
}
function changeAge() {
person.value.age += 1;
}
function changePerson() {
person.value = {name: 'yyy', age: 90};
}
watch(person,(newValue, oldValue) => {
console.log('person is Changed', newValue, oldValue);
},{deep: true, immediated: true})
</script>
<template>
<div>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<button @click="changeName">改名字</button>
<button @click="changeAge">改年龄</button>
<button @click="changePerson">改人</button>
</div>
</template>

若按情况一的写法,watch只会监视对象的地址值,而不关心对象属性的变化
若想监视内部属性,则需要手动开启深度监视{deep: true}
{immediated: true}可以在开始是调用warch的回调函数,oldValue等于undefined

情况三

watch监视reactive定义的对象类型数据

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
<script setup lang="ts"> 
import {reactive, watch} from 'vue';

let person = {
name: 'xxx',
age: 18
}

function changeName() {
person.name += '~';
}
function changeAge() {
person.age += 1;
}
function changePerson() {
Object.assign(person, {name: 'yyy', age: 90});
}

watch(person, () => {
console.log('person is Changed', newValue, oldValue);
})
</script>
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<button @click="changeName">改名字</button>
<button @click="changeAge">改年龄</button>
<button @click="changePerson">改人</button>
</div>
</template>

默认开启深度监视,且不可关闭

情况四

watch监视refreactive定义的对象类型数据中的某个属性,注意以下几点:

  1. 若该属性值不是对象类型,需要改写为函数形式
  2. 若该属性值仍是对象类型,可以直接写,但建议也写成函数。否则在该属性整体改变时,​依赖追踪失效​导致监听不触发
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script setup lang="ts"> 
import {reactive, watch} from 'vue';
let person = reactive({
name: 'xxx',
age: 18,
cars: {
car1: '奔驰',
car2: '宝马'
}
})


function changeName() {
person.name += '~';
}
function changeAge() {
person.age += 1;
}
function changeCar1() {
person.cars.car1 = '小米';
}
function changeCar2() {
person.cars.car2 = '理想';
}
function changeCars() {
person.cars = {
car1: '爱玛',
car2: '雅迪'
}
}

watch(() => person.name, (newValue, oldValue) => {
console.log("name is changed")
})
watch(() => person.cars, (newValue, oldValue) => {
console.log("cars ")
},{deep: true})
</script>
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>汽车:{{person.cars.car1}},{{person.cars.car2}}</h2>
<button @click="changeName">改名字</button>
<button @click="changeAge">改年龄</button>
<button @click="changeCar1">改第一台车</button>
<button @click="changeCar2">改第二台车</button>
<button @click="changeCars">改全部车</button>
</div>
</template>

情况五

watch监视上述多个数据

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script setup lang="ts"> 
import {reactive, watch} from 'vue';
let person = reactive({
name: 'xxx',
age: 18,
cars: {
car1: '奔驰',
car2: '宝马'
}
})


function changeName() {
person.name += '~';
}
function changeAge() {
person.age +='1';
}
function changeCar1() {
person.cars.car1 = '小米';
}
function changeCar2() {
person.cars.car2 = '理想';
}
function changeCars() {
person.cars = {
car1: '爱玛',
car2: '雅迪'
}
}

watch([() => person.name, () => person.cars.car1], (newValue, oldValue) => {
console.log("name is changed")
})
</script>
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>汽车:{{person.cars.car1}},{{person.cars.car2}}</h2>
<button @click="changeName">改名字</button>
<button @click="changeAge">改年龄</button>
<button @click="changeCar1">改第一台车</button>
<button @click="changeCar2">改第二台车</button>
<button @click="changeCars">改全部车</button>
</div>
</template>

5. watchEffect()

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
34
35
<script setup lang="ts"> 
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, hejight], (value) => {
let [newTemp, Height] = value;
if(newTemp >= 60 || newHeight >= 80) {
console.log('@@@');
}
console.log(newTemp, Height);
})
// watchEffect 实现
watchEffect(() => {
if(temp.value >= 60 || height.value >= 80){
console.log('@@@');
}
})
</script>
<template>
<div class="water">
<h2>当前水温为:{{temp}}</h2>
<h2>当前水位为:{{height}}</h2>
<button @click="changeTemp">temp+10</button>
<button @click="changeHeight">height+10</button>
</div>
</template>

自动追踪回调函数内使用的响应式数据(如 temp.valueheight.value),无需显式声明依赖。初始化时立即执行一次回调,用于捕获初始状态。无法直接获取变化前的值

6. 标签的ref属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts"> 
import {ref} from 'vue';

let title2 = ref();

function showLog() {
console.log(title2);
}
</script>
<template>
<div class="water">
<h1>中国</h1>
<h2 ref="title2">青海</h2>
<h3>西宁</h3>
<button @click="showLog">click me!</button>
</div>
</template>

推荐使用标签的ref属性来获取元素,而不是根据id进行DOM操作。因为ref容器是局部的,而DOM操作容易获取到其他组件的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {ref, defineExpose} from 'vue';

let title2 = ref();
let a = ref(0);
let b = ref(1);
let c = ref(2);
function showLog() {
console.log(title2);
}

defineExpose({a, b, c});
</script>
<template>
<div class="water">
<h1>中国</h1>
<h2 ref="title2">青海</h2>
<h3>西宁</h3>
<button @click="showLog">click me!</button>
</div>
</template>

当父组件通过ref属性来获得子组件的元素时,默认是无法获取任何元素的。必须在子组件通过defineExpose()将对象暴露

7. props

index.ts

1
2
3
4
5
6
7
export interface PersonInterface {
id: string,
name: string,
age: number
}

export type Persons = PersonInterface[];

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts"> 
import Person from './components/Person.vue';
import {type Persons} from '@/types'
import {reactive} from 'vue';

let PersonList = reactive<Persons>([
{id: 'dhfgajsh01', name: 'xxx', age: 18},
{id: 'dhfgajsh02', name: 'yyy', age: 19},
{id: 'dhfgajsh03', name: 'zzz', age: 17}
])
</script>
<template>
<Person/ :List="PersonList">// 把列表传给Person组件
</template>

Person.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts"> 
import {defineProps} from 'vue';
import {Persons} from '@/types'

// 只接收List
defineProps(['List']); // 必须写成数组的形式

// 接收List + 类型限制
defineProps<{List: Person}>();

// 接收List + 类型限制 + 限制必要性 + 指定默认值
const { List = [{ name: '默认姓名', age: 18 }] } = defineProps<{ List?: Person[] }>();
</script>
<template>
<div class="persons">
<ul>
<li v-for = "p in List" :key = "p.id">
{{p.name}}--{{p.age}}
</li>
</ul>
</div>
</template>

8. 生命周期

组件的生命周期
创建 挂载 更新 销毁

1. Vue2 生命周期

生命周期 钩子
创建 beforeCreate(),created()
挂载 beforeMount(),mounted()
更新 beforeUpdate,updated()
销毁 beforeDestroy(),destroyed()

2. Vue3 生命周期

生命周期 钩子
创建 在setup函数中便是创建阶段
挂载 onBeforeMount(),onMounted()
更新 onBeforeUpdate,onUpdated()
销毁 onBeforeUnmount(),onUnmounted()

9. Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {ref} from 'vue';

let sum = ref(0);
let count = ref(1);

function changeSum() {
sum.value += 10;
}
function changeCount() {
count.value += 1;
}
</script>
<template>
<div class="water">
<h2>求和:{{ sum }}</h2>
<h2>计数:{{ count }}</h2>
<button @click="changeSum">sum+10</button>
<button @click="changeCount">count+1</button>
</div>
</template>

通过Hooks将上述组件脚本中的不同功能的数据和函数封装

  • useSum.ts:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {ref} from 'vue';

    export default function(){
    let sum = ref(0);

    function changeSum() {
    sum.value += 10;
    }

    return {sum, changeSum};
    }
  • useCount.ts:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {ref} from 'vue';

    export default function(){
    let count = ref(1);

    function changeCount() {
    count.value += 1;
    }

    return {count, changeCount};
    }
  • Math.vue:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script setup lang="ts"> 
    import useSum from '@/hooks/useSum';
    import useCount from '@/hooks/useCount';

    const {sum, changeSum} = useSum();
    const {count, changeCount} = useCount();
    </script>
    <template>
    <div class="water">
    <h2>求和:{{ sum }}</h2>
    <h2>计数:{{ count }}</h2>
    <button @click="changeSum">sum+10</button>
    <button @click="changeCount">count+1</button>
    </div>
    </template>

四、路由

1. 安装路由器组件

1
npm i vue-router

2. 基本切换效果

src/App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import {RouterView, RouterLink} from 'vue-router';
</script>
<template>
<div class="app">
<div class="navigate">
<RouterLink to="/about" active-class="active">About</RouterLink >
<RouterLink to="/home" active-class="active">Home</RouterLink >
<RouterLink to="/news" active-class="active">News</RouterLink >
</div>
<div>
<RouterView></RouterView>
</div>
</div>
</template>

src/views/About.vue:

1
2
3
4
5
6
7
<script setup lang="ts"> 
</script>
<template>
<div class="about">
<p>这是一个测试组件</p>
</div>
</template>

src/views/Home.vue:

1
2
3
4
5
6
7
<script setup lang="ts"> 
</script>
<template>
<div class="home">
<p>主页</p>
</div>
</template>

src/views/News.vue:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts"> 
</script>
<template>
<div class="news">
<ul>
<li><a href="#">news 1</a></li>
<li><a href="#">news 2</a></li>
<li><a href="#">news 3</a></li>
</ul>
</div>
</template>

src/router/index.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/viewsviews/Home.vue';
import About from '@/viewsviews/About.vue';
import News from '@/viewsviews/News.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
path: '/home',
conponent: Home
},{
path: '/about',
conponent: About
},{
path: '/news',
conponent: News
}
]
});

export default reouter

src/main.ts:

1
2
3
4
5
6
7
8
import {createApp} from 'vue';
import App from './App.vue';

import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

3. 注意事项

  1. 路由组件一般放在pagesviews文件夹,一般组件放在components文件夹。
  2. 通过点击导航,视觉效果消失了的路由组件,默认是被卸载的,需要时会重新挂载

4. 路由器工作模式

  1. history模式

    优点:URL更加美观,不带#,更接近传统网站URL
    缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会用404错误。

  2. hash模式

    优点:兼容性更好,因为不需要服务端处理路径。
    缺点:URL#不太美观,而且在SEO优化方面相对较差。

5. to的两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts"> 
import {RouterView, RouterLink} from 'vue-router';
</script>
<template>
<div class="app">
<div class="navigate">
<RouterLink to="/about" active-class="active">About</RouterLink >
<RouterLink :to="{path: '/home'}" active-class="active">Home</RouterLink >
<RouterLink :to="{name: '关于'}" active-class="active">News</RouterLink >
// 下面会讲到命名路由
</div>
<div>
<RouterView></RouterView>
</div>
</div>
</template>

6. 命名路由

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
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import News from '@/views/News.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
name: '主页',
path: '/home',
conponent: Home
},{
name: '关于',
path: '/about',
conponent: About
},{
name: '新闻',
path: '/news',
conponent: News
}
]
});

export default router;

7. 嵌套路由

src/views/News.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {react} from 'vue';

import {RouterView, RouterLink} from 'vue-router';

const newsList = reactive([
{id: 'asda001', title:'如何学好前端', content:'菜就多练'},
{id: 'asda002', title:'最好的语言', content:'PHP'},
{id: 'asda003', title:'国庆假期该干什么', content:'睡觉'}
])
</script>
<template>
<div class="news">
<ul>
<li v-for="news in newsList", key="news.id"><RouterLink to="/news/detail">{{ news.title }}</RouterLink></li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>

src/views/Detail.vue:

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts"> 

</script>
<template>
<ul class="news-list">
<li>编号:xxx</li>
<li>标题:xxx</li>
<li>内容:xxx</li>
</ul>
</template>

src/router/index.ts:

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
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import News from '@/views/News.vue';

import Detail from '@/views/Detail.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
name: '主页',
path: '/home',
conponent: Home
},{
name: '关于',
path: '/about',
conponent: About,
children: [{
name: '详情',
path: 'detail', // 子路径无前导斜杠
component: Detail
}
]
},{
name: '新闻',
path: '/news',
conponent: News
}
]
});

export default router;

8. query参数

src/views/Detail.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts"> 
import {getRoute} from 'vue-router';

const route = uesRoute();

</script>
<template>
<ul class="news-list">
<li>编号:{{ route.query.id }}</li>
<li>标题:{{ route.query.title }}</li>
<li>内容:{{ route.query.content }}</li>
</ul>
</template>

src/views/News.vue

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
34
35
36
<script setup lang="ts"> 
import {react} from 'vue';

import {RouterView, RouterLink} from 'vue-router';

const newsList = reactive([
{id: 'asda001', title:'如何学好前端', content:'菜就多练'},
{id: 'asda002', title:'最好的语言', content:'PHP'},
{id: 'asda003', title:'国庆假期该干什么', content:'睡觉'}
])
</script>
<template>
<div class="news">
<ul>
<!-- 第一种写法 -->
<li v-for="news in newsList", key="news.id"><RouterLink :to="`/news/detail?$id={news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}</RouterLink></li>
<!-- 第二种写法 -->
<li v-for="news in newsList", key="news.id">
<RouterLink
:to="{
path: '/news/detail',
query:{
id: news.id,
titile: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink></li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>

9. params参数

src/views/Detail.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts"> 
import {useRoute} from 'vue-router';

const route = uesRoute();

</script>
<template>
<ul class="news-list">
<li>编号:{{ route.parmas.id }}</li>
<li>标题:{{ route.parmas.title }}</li>
<li>内容:{{ route.parmas.content }}</li>
</ul>
</template>

src/views/News.vue

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
34
35
36
<script setup lang="ts"> 
import {react} from 'vue';

import {RouterView, RouterLink} from 'vue-router';

const newsList = reactive([
{id: 'asda001', title:'如何学好前端', content:'菜就多练'},
{id: 'asda002', title:'最好的语言', content:'PHP'},
{id: 'asda003', title:'国庆假期该干什么', content:'睡觉'}
])
</script>
<template>
<div class="news">
<ul>
<!-- 第一种写法 -->
<li v-for="news in newsList", key="news.id"><RouterLink :to=`"/news/detail/${new.id}/${news.title}/${news.content}"`>{{ news.title }}</RouterLink></li>
<!-- 第二种写法 -->
<li v-for="news in newsList", key="news.id">
<RouterLink
:to="{
name: '详情',
query:{
id: news.id,
titile: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink></li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>

src/router/index.ts:

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
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import News from '@/views/News.vue';

import Detail from '@/views/Detail.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
name: '主页',
path: '/home',
conponent: Home
},{
name: '关于',
path: '/about',
conponent: About,
children: [{
name: '详情',
path: 'detail/:id/:title/:content?', // 需用占位符占位,问号表示可传可不传
component: Detail
}
]
},{
name: '新闻',
path: '/news',
conponent: News
}
]
});

export default router;

传递params参数时,必须在路由规则里提前占位
传递params参数时,to对象写法传参必须用name,不能用path

10. props配置

src/views/Detail.vue:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts"> 

defineProps(['id', 'title', 'content']);
</script>
<template>
<ul class="news-list">
<li>编号:{{ id }}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</template>

src/views/News.vue

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
34
35
36
<script setup lang="ts"> 
import {react} from 'vue';

import {RouterView, RouterLink} from 'vue-router';

const newsList = reactive([
{id: 'asda001', title:'如何学好前端', content:'菜就多练'},
{id: 'asda002', title:'最好的语言', content:'PHP'},
{id: 'asda003', title:'国庆假期该干什么', content:'睡觉'}
])
</script>
<template>
<div class="news">
<ul>
<!-- 第一种写法 -->
<li v-for="news in newsList", key="news.id"><RouterLink :to=`"/news/detail/${new.id}/${news.title}/${news.content}"`>{{ news.title }}</RouterLink></li>
<!-- 第二种写法 -->
<li v-for="news in newsList", key="news.id">
<RouterLink
:to="{
name: '详情',
query:{
id: news.id,
titile: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink></li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>

src/router/index.ts:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import News from '@/views/News.vue';

import Detail from '@/views/Detail.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
name: '主页',
path: '/home',
conponent: Home
},{
name: '关于',
path: '/about',
conponent: About,
children: [{
name: '详情',
path: 'detail/:id/:title/:content?', // 需用占位符占位,问号表示可传可不传
component: Detail,
// 第一种方法:将路由收到的pramas参数作为props传给路由组件
props: true,
// 第二种方法:函数写法,自由决定将什么参数作为props传给路由组件
props(route) {
return {
return reoute.query;
}
}
// 第三种方法:对象写法,自由决定将什么参数作为props传给路由组件(写死的,应用场景少)
props: {
a: 100,
b: 200,
c: 300
}
}
]
},{
name: '新闻',
path: '/news',
conponent: News
}
]
});

export default router;

10. replace属性

src/App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import {RouterView, RouterLink} from 'vue-router';
</script>
<template>
<div class="app">
<div class="navigate">
<RouterLink replace to="/about" active-class="active">About</RouterLink >
<RouterLink to="/home" active-class="active">Home</RouterLink >
<RouterLink to="/news" active-class="active">News</RouterLink >
</div>
<div>
<RouterView></RouterView>
</div>
</div>
</template>

加上replace属性后,跳转后不再可以使用浏览器的回退回退上一页。默认情况是push模式

10. 编程式路由导航

src/views/Home.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts"> 
import {onMounted} from 'vue';
import {useRouter} from 'vue-router';

const router = useRouter();

onMounted(() => {
setTimeout(() => {
router.push('/news')
},3000);
})
</script>
<template>
<div class="home">
<p>主页</p>
</div>
</template>

push()传参方式和<RouterLink>to属性相同。

11. 重定向

src/router/index.ts:

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
34
35
import {createRouter, createWebHistory} from 'vue-router';

import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import News from '@/views/News.vue';

import Detail from '@/views/Detail.vue';

const router = createRouter({
history: createWebHistory(),
routes:[{
name: '主页',
path: '/home',
conponent: Home
},{
name: '关于',
path: '/about',
conponent: About,
children: [{
name: '详情',
path: 'detail/:id/:title/:content?', // 需用占位符占位,问号表示可传可不传
component: Detail
]
},{
name: '新闻',
path: '/news',
conponent: News
},{
path: '/',
redirect: '/home'
}
]
});

export default router;

path路径重定向到redirect路径

五、pinia

1. 搭建pinia环境

1
npm i pinia

src/main.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {createApp} from 'vue';
import App from './App.vue';

// 引入pinia
import {creatPinia} from 'pinia';

const app = createApp(App);

// 创建pinia
const pinia = createPinia();
// 安装pinia
app.use(pinia);

app.mount('#app');

2. 存储读取数据

src/store/Count.ts:

1
2
3
4
5
6
7
8
9
import {defineStore} from 'pinia';

export const useCountStore = defineStore('count', {
state() {
return {
count: 6
};
}
});

src/components/Count.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts"> 
import {ref} from 'vue';

import {useCountStore} from '@/store/Count';

const countStore = useCountStore();

function changeCount() {
countStore.count += 1;
}
</script>
<template>
<div class="water">
<h2>计数:{{ countStore.count }}</h2>
<button @click="changeCount">countStore.count+1</button>
</div>
</template>

3. 修改数据

src/components/Count.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts"> 
import {ref} from 'vue';

import {useCountStore} from '@/store/Count';

const countStore = useCountStore();

function changeCount() {
// 第一种方式
countStore.count += 1;
// 第二种方式
countStore.$patch({
count += 1
});
// 第三种方式
countStore.add(10 );
}
</script>
<template>
<div class="water">
<h2>计数:{{ countStore.count }}</h2>
<button @click="changeCount">countStore.count+1</button>
</div>
</template>

src/store/Count.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {defineStore} from 'pinia';

export const useCountStore = defineStore('count', {
actions: {
add(value){
this.count += value;
}
},
state() {
return {
count: 6
};
}
});

直接修改​​:直接操作状态属性每次修改都会触发独立的响应式更新,在 DevTools 中会产生多条记录
$patch批量修改​​:将多个修改合并为一次响应式更新,在 DevTools 中只显示一条记录,减少响应式触发次数,适合性能优化
Action 修改​​:在 Store 中定义 actions方法,支持参数传递、异步操作和复杂业务逻辑,提高代码复用性和可维护性

4. storeToRefs()

src/components/Count.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {ref} from 'vue';
import {useCountStore} from '@/store/Count';
import {storeToRefs} from 'pinia';

const countStore = useCountStore();

const {count} = storeToRefs(countStore);

function changeCount() {
countStore.$patch({
count += 1
});
}
</script>
<template>
<div class="water">
<h2>计数:{{ count }}</h2>
<button @click="changeCount">count+1</button>
</div>
</template>

对 Pinia Store 进行解构赋值时,若直接使用 toRefs()会将所有属性(包括方法)强制转换为响应式引用,造成不必要的性能开销。推荐使用 storeToRefs(),它仅针对 state 和 getters 生成响应式引用,避免对 actions 等非状态数据做无效处理,从而优化性能。

5. getters

src/store/Count.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {defineStore} from 'pinia';

export const useCountStore = defineStore('count', {
actions: {
add(value){
this.count += value;
}
},
state() {
return {
count: 6
};
},
getters:{
bigCount(state){
return state.count * 10;
},
smallCount(state){
return state.count / 10;
}
}
});

6. `$subscribe()

src/components/Count.vue:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script setup lang="ts"> 
import {ref} from 'vue';
import {useCountStore} from '@/store/Count';
import {storeToRefs} from 'pinia';

const countStore = useCountStore();

const {count} = storeToRefs(countStore);

countStore.$subscribe((mutate, state) => {
console.log(countStore中数据发生了变化);
localStorage.setItem('count',JSON.stringify(state.count)); // 当值发生变化后,将新数据存入浏览器缓存
})
function changeCount() {
countStore.$patch({
count += 1
});
}
</script>
<template>
<div class="water">
<h2>计数:{{ count }}</h2>
<button @click="changeCount">count+1</button>
</div>
</template>
````
`src/store/Count.ts`:
```typescript
import {defineStore} from 'pinia';

export const useCountStore = defineStore('count', {
actions: {
add(value){
this.count += value;
}
},
state() {
return {
count: JSON.parse(localStorage.getItem('count') as string) || []; // count的值优先从用户的浏览器缓存中获取
};
},
getters:{
bigCount(state){
return state.count * 10;
},
smallCount(state){
return state.count / 10;
}
}
});

通过订阅可以实现网页刷新数据不丢失

7. store的组合式写法

src/store/Count.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
import {defineStore} from 'pinia';

import {reactive} from 'vue';

export const useCountStore = defineStore('count', () => {
const count = reactive(
JSON.parse(localStorage.getItem('count') as string) || []
)
add(value){
count += value;
}
return {count, add};
});

六、组件通信

1. props

src/components/Father.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup lang="ts"> 
import Child from './Child.vue';
import {ref} from 'vue';

let car = ref('宝马');
let toy = ref('');

function getToy(value:string){
toy.value = value;
}
</script>
<template>
<div class="father">
<h2>父组件</h2>
<h3>孩子的玩具:{{ toy }}</h3>
<Child :car="car" :sendToy="getToy"/>
</div>
</template>

src/components/Child.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import {ref} from 'vue';

let toy = ref('乐高');

defineProps(['car','sendToy']);
</script>
<template>
<div class="child">
<h2>子组件</h2>
<h3>玩具:{{ toy }}</h3>
<h3>礼物:{{ car }}</h3>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>

父组件在创建子组件时,直接在子标签中加属性即可将参数传给子组件,子组件只需用defineProps()方法接收父组件给的参数即可。
子组件给父组件传参数,则需要父组件将一函数传给子组件,子组件通过该函数的传参将参数传给父组件。

2. 自定义事件

src/components/Father.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup lang="ts"> 
import Child from './Child.vue';
import {ref} from 'vue';


let toy = ref('');

function getToy(value: string) {
toy.value = value;
}
</script>
<template>
<div class="father">
<h2>父组件</h2>
<h3>孩子的玩具:{{ toy }}</h3>
<Child @send-toy="getToy"/>
</div>
</template>

src/components/Child.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts"> 
import {ref} from 'vue';

let toy = ref('乐高');

const emit = defineEmits(['sendToy']);
</script>
<template>
<div class="child">
<h2>子组件</h2>
<h3>玩具:{{ toy }}</h3>
<button @click="emit('send-toy',toy)">把玩具给父亲</button>
</div>
</template>

3. mitt

1
npm i mitt

src/utils/emitter.ts:

1
2
3
4
5
6
7
8
// 引入
import mitt from 'mitt';

// 调用
const emitter = mitt();

// 暴露
export defalut emitter;

src/components/Child1.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts"> 
import {ref} from 'vue';
import emitter from '@/utils/emitter';

let toy = ref('乐高');

</script>
<template>
<div class="child">
<h2>子1组件</h2>
<h3>玩具:{{ toy }}</h3>
<button @click="emitter.emit('sef-toy',toy)">把玩具给弟弟</button>
</div>
</template>

src/components/Child2.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts"> 
import {ref, onUnmounted} from 'vue';
import emitter from '@/utils/emitter';

let computer = ref('联想');
let toy = ref('');

emitter.on('send-toy',(value) => {
toy.value = value;
})

onUnmounted(() => {
emitter.off('send-toy'); // 在该组件卸载时,解绑事件
})
</script>
<template>
<div class="child">
<h2>子2组件</h2>
<h3>电脑:{{ computer }}</h3>
<h3>玩具:{{ toy }}</h3>
</div>
</template>

4. v-model

src/components/Father.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script setup lang="ts"> 
import {ref} from 'vue';
import ming
let username = ref('zhangsan');
</script>
<template>
<div class="father">
<h2>父组件</h2>
<!-- v-model用在HTML标签,以下两种写法等效 -->
<input type="text" v-model="username">
<input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value">
<!-- v-model用在组件标签,以下两种写法等效 -->
<MingInput v-model="username"/>
<MingInput :modeValue="username" @update:modeValue="username = $event"/>
<!-- modeValue值是可以自定义的 -->
<MingInput v-model:xxx="username"/>
<MingInput :xxx="username" @update:xxx="username = $event"/>
</div>
</template>

src/components/MingInput.vue:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts"> 
import {ref} from 'vue';

let username = ref('zhangsan');

defineProps(['modeValue']);
const emit = defineProps(['update:modeValue']);
</script>
<template>
<input type="text" :value="modelValue" @input="emit('update:modeValue',(<HTMLInputElement>$event.target).value)">
</template>

5. $attrs

src/components/Father.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup lang="ts"> 
import Child from './Child.vue';
import {ref} from 'vue';

let a = ref(1);
let b = ref(2);
let c = ref(3);
let d = ref(4);
</script>
<template>
<div class="father">
<h2>父组件</h2>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>

<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x: 100, y: 200}"/>
</div>
</template>

src/components/Child.vue:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts"> 
import {ref} from 'vue';
import GrandChild from './GrandChild.vue';

</script>
<template>
<div class="child">
<h2>子组件</h2>
<GrandChild v-bind="$attrs"/>
</div>
</template>

src/components/GrandChild.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup lang="ts"> 
import {ref} from 'vue';

let toy = ref('乐高');

defineProps(['a', 'b', 'c', 'd', 'x', 'y']);
</script>
<template>
<div class="grandChild">
<h2>孙组件</h2>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
</div>
</template>

父组件给子组件传的参数,若没有被defineProps()接收,将会以一个对象的形式保存在attrs里,通过$attrs调用该对象。

6. $refs$parent

src/components/Father.vue:

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
<script setup lang="ts"> 
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
import {ref} from 'vue';

let house = ref(6);

defineExpose({'house'});

function giveBookAllChild(refs: {[key: string]: any}){
for(let key in refs){
refs[key].book += 3;
}
}
</script>
<template>
<div class="father">
<h2>父组件</h2>
<h3>房产:{{ house }}</h3>

<Child1 ref='c1'/>
<Child2 ref='c2'/>

<button @click="giveBookAllChild($refs)">给全部孩子买书</button>
</div>
</template>

src/components/Child1.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts"> 
import {ref} from 'vue';

let computer = ref('华为');
let book = ref(4);

defineExpose({'computer', 'book'});

function minusHouse(parent: {[key: string]: any}) {
parent.house -= 1;
}
</script>
<template>
<div class="child1">
<h2>子组件</h2>
<h3>电脑:{{ computer }}</h3>
<h3>书:{{ book }}</h3>

<button @click="minusHouse($parent)">卖掉父亲一套房产</button>
</div>
</template>

src/components/Child2.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import {ref} from 'vue';

let toy = ref('乐高');
let book = ref(4);

defineExpose({'toy', 'book'});
</script>
<template>
<div class="child2">
<h2>子组件</h2>
<h3>玩具:{{ toy }}</h3>
<h3>书:{{ book }}</h3>
</div>
</template>

$refs包含所有被ref属性标识的DOM元素或组件实例
$parent当前组件的父组件实例对象

7. provideinject

src/components/Father.vue:

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
<script setup lang="ts"> 
import Child from './Child.vue';
import {ref,reactive,provide} from 'vue';

let car = rective({
brand: '宝马',
price: 100
});
let money = ref(100);

function moneyContext(value: number) {
money.value += value;
}

// 向后代提供数据
provide('money',{money, moneyContext});
provide('car',car);

</script>
<template>
<div class="father">
<h2>父组件</h2>
<h4>车:{{ car.brand }},价格:{{ car.moeny }}w</h4>
<h4>钱:{{ money }}w</h4>

<Child/>
</div>
</template>

src/components/Child.vue:

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts"> 
import GrandChild from './GrandChild.vue';

</script>
<template>
<div class="child">
<h2>子组件</h2>
<GrandChild/>
</div>
</template>

src/components/GrandChild.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import {inject} from 'vue';

let {money, updateMoney} = inject('money', {money: 0,updateMoney: (x: number) => {}});
let car = inject('car',{brand: 'unkown', price: 0});
</script>
<template>
<div class="grandChild">
<h2>孙组件</h2>
<h4>车:{{ car.brand }},价格:{{ car.moeny }}w</h4>
<h4>钱:{{ money }}w</h4>

<button @click="updateMoney(10)">修改money</button>
</div>
</template>

8. 插槽

1. 默认插槽

src/components/Father.vue:

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
<script setup lang="ts"> 
import Category from './Category.vue';
import { ref, reactive } from 'vue';

let games = reactive([
{id: 'adfasdfasd01', name: '我的世界'},
{id: 'adfasdfasd02', name: '王者荣耀'},
{id: 'adfasdfasd03', name: '英雄联盟'},
{id: 'adfasdfasd04', name: '绝地求生'}
]);
let imgUrl = ref('http:// /');
let videoUrl = ref('http:// /');
</script>
<template>
<div class="father">
<Category title="热门游戏列表">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>

<Category title="今日美食城市">
<img :src="imgUrl" alt="">
</Category>

<Category title="今日影视推荐">
<video :src="videoUrl"></video controls>
</Category>
</div>
</template>

src/components/Category.vue:

1
2
3
4
5
6
7
8
9
<script setup lang="ts"> 
defineProps(['title']);
</script>
<template>
<div class="category">
<h2>{{ title }}</h2>
<slot> 默认内容 </slot>
</div>
</template>

在子组件中使用 <slot></slot>定义​​内容分发出口​​,父组件调用时可在子组件标签内部插入任意模板内容(如 HTML、动态数据、其他组件),这些内容将自动替换子组件中的 <slot>位置。若父组件未提供内容,则显示 <slot>标签内的默认内容

2. 具名插槽

src/components/Father.vue:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script setup lang="ts"> 
import Category from './Category.vue';
import { ref, reactive } from 'vue';

let games = reactive([
{id: 'adfasdfasd01', name: '我的世界'},
{id: 'adfasdfasd02', name: '王者荣耀'},
{id: 'adfasdfasd03', name: '英雄联盟'},
{id: 'adfasdfasd04', name: '绝地求生'}
]);
let imgUrl = ref('http:// /');
let videoUrl = ref('http:// /');
</script>
<template>
<div class="father">

<Category>
<template v-slot:s1>
<h2>{{ title }}</h2>
</template>
<template v-slot:s2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
</Category>

<Category>
<template v-slot:s1>
<h2>{{ title }}</h2>
</template>
<template v-slot:s2>
<img :src="imgUrl" alt="">
</template>
</Category>

<Category>
<template #s1>
<h2>{{ title }}</h2>
</template>
<template #s2>
<video :src="videoUrl"></video controls>
</template>
</Category>

</div>
</template>

src/components/Category.vue:

1
2
3
4
5
6
7
8
<script setup lang="ts"> 
</script>
<template>
<div class="category">
<slot name="s1"> 默认内容1 </slot>
<slot name="s2"> 默认内容2 </slot>
</div>
</template>

在子组件中通过 <slot name="xxx">定义​​多个命名分发出口​​,父组件调用时需通过 <template #xxx><template v-slot:xxx>将内容精准插入对应名称的插槽位置。

3. 作用域插槽

src/components/Father.vue:

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
<script setup lang="ts"> 
import game from './Game.vue';
</script>
<template>
<div class="father">
<div class="content">
<Game>
<template v-slot="params">
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
</Game>
<Game>
<template v-slot="params">
<ol>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ol>
</template>
</Game>
<Game>
<template v-slot="{games}">
<h3 v-for="g in games" :key="g.id">{{ g.name }}</h3>
</template>
</Game>
</div>
</div>
</template>

src/components/Game.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts"> 
import { reactive } from 'vue';

let games = reactive([
{id: 'adfasdfasd01', name: '我的世界'},
{id: 'adfasdfasd02', name: '王者荣耀'},
{id: 'adfasdfasd03', name: '英雄联盟'},
{id: 'adfasdfasd04', name: '绝地求生'}
]);
</script>
<template>
<div class="game">
<slot :games="games" :x="text" :y="text"></slot>
</div>
</template>

子组件​通过 <slot>v-bind绑定数据(如 :games="games"),将内部数据暴露给父组件。
​父组件​​通过 v-slot接收子组件传递的数据(如 params或解构 { games }),并自定义渲染逻辑。

七、其他API

1. shallowRefshallowReactive

API 响应式层级 典型场景 示例
shallowRef .value DOM 元素引用、大型对象性能优化 const domRef = shallowRef(null)
shallowReactive 仅对象顶层属性 表单字段(仅需监听字段增减,无需深度响应) const form = shallowReactive({ fields: {} })

​性能优化​​:避免深度递归转换嵌套属性,减少内存和计算开销。
​手动触发更新​​:shallowRef需通过 triggerRef()强制更新视图(如直接修改 .value内部属性时)。

2. readonlyshallowReadonly

​区别与选择​

特性 readonly shallowReadonly
​只读深度​ 递归所有嵌套属性 仅顶层属性
​性能影响​ 较高(递归处理) 较低
​使用场景​ 全局配置保护 部分保护 + 嵌套数据可修改

​示例代码​

1
2
3
4
5
6
7
// 深度只读(保护整个对象)
const config = readonly({ api: { endpoint: 'https://api.com' } });
config.api.endpoint = ''; // 警告并阻止修改

// 浅层只读(允许修改嵌套属性)
const user = shallowReadonly({ profile: { name: 'Alice' } });
user.profile.name = 'Bob'; // 可修改

3. toRawmarkRaw

toRaw​​:

返回响应式对象的原始非代理对象,操作原始对象不会触发视图更新。
​场景​​:与非 Vue 库交互时避免响应式干扰(如传给 D3.js)。

markRaw​​:

标记对象永不转为响应式,适用于静态数据或第三方类库实例。

1
2
const staticData = markRaw([...]);
const state = reactive({ list: staticData }); // list 保持非响应式

4. customRef

自定义 ref的依赖跟踪(track)和更新触发(trigger)逻辑。

1. 防抖/节流​:延迟触发视图更新

1
2
3
4
5
6
7
8
9
10
11
12
function useDebouncedRef(value, delay) {
return customRef((track, trigger) => {
let timer;
return {
get() { track(); return value; },
set(newVal) {
clearTimeout(timer);
timer = setTimeout(() => { value = newVal; trigger(); }, delay);
}
};
});
}

2. 本地存储同步:自动读写 localStorage

八、Vue3新组件

1. teleport

1
2
3
4
5
<teleport to='body'>
<div>
<h3>hhh</h3>
</div>
</teleport>

<teleport></teleport>标签可以将对应内容传送到html对应位置

2. suspense

src/components/Father.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script setup lang="ts"> 
import {Suspense} from 'vue';
import Child from './Child.vue'
</script>
<template>
<div class="app">
<h2>父组件标题</h2>

<Suspense>
<template #default>
<Child/>
</template>
<template #fallback>
<h2>加载中</h2>
</template>
</Suspense>

</div>
</template>

src/components/MingInput.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts"> 
import {ref} from 'vue';
import axios from 'axios';

let sum = ref(0);
let {data: {content}} = await axios.get('http://');
console.log(content);
</script>
<template>
<div>
<h2></h2>
</div>
</template>

在子组件有异步操作时,异步完毕后显示#default的,异步操作进行中显示#fallback的内容

3. 全局API转移到应用对象

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

4. 其他