vue3文档

50 阅读22分钟

## 编码规范

在vue3中

1、编码语言:javascript、typescript

2、代码风格:组合式API(compostion API)、选项式API(传统的vue2项目)

3、简写形式:setup语法糖

### 选型

1、`TypeScript` + `composition API` + `setup语法糖`

2、node版本:官方要求 `18.3 +`,我们选择使用:`v20.13.1(npm:10.5.2)稳定版`

```

nvm install 20.13.1

nvm use 20.13.1

node -v

npm -v

```

3、typescript版本:使用最新稳定版:5.5.3

```

// 全局安装

npm install -g typescript@5.5.3

tsc -version

```

4、所有语法及工具全部采用官方推荐,且接近于element-plus-admin开源项目

### 简介

#### 性能提升

- 打包大小减少:41%

- 初次渲染快:51%,更新渲染快133%

内存减少:54%

#### 源码升级

使用Proxy代替defineProperty实现响应式

重写虚拟DOM的实现和Tree-Shaking(通过分析‌JavaScript代码的依赖关系和执行情况,动态地移除未使用的代码部分,从而减小最终代码的体积和提高性能)

#### 兼容TypeScript

vue3**更好**的兼容TypeScript

#### 新特性

1、使用了新的组合是API(Compostion API)

- setup

- ref与reactive

- computed与watch

2、新的内置组件

- Fragment

- Teleport

- Suspense

3、其它

- 新的生命周期

- ....

## 需要掌握知识

[Vue3](v3.vuejs.org/)

[Pinia](pinia.vuejs.org/)

[TypeScript](www.typescriptlang.org/)

[Vue-router](next.router.vuejs.org/)

[Element-plus](element-plus.org/)

[Es6](es6.ruanyifeng.com/)

[Vitejs](vitejs.dev/)

[unocss](unocss.dev/)

[Axios](axios-http.com/)

## 项目创建

1、基于vue-cli创建

跟vue2一样

2、基于vite创建(官方推荐,element-plus-admin项目也是这么建的)

```

npm create vue@latest

```

# 核心

## 组合式API

Vue2使用的是选项式 API ,核心是data声明响应式状态,并返回一个对象函数,创建新组件实例的时候调用此函数,此对象的所有顶层属性都被代理到组件实例上,(即方法和生命周期钩子中的 this)

`setup() 钩子是在组件中使用组合式 API 的入口`

[官方链接](cn.vuejs.org/guide/extra…)

### setup()

`setup()` 钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:

1. 需要在非单文件组件中使用组合式 API 时。

2. 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。

> [!CAUTION]

>

> 推荐通过

[官方链接](cn.vuejs.org/api/composi…)

#### 示例

```v

// setup()函数方式写法,需要写返回值,且暴露大量的状态和方法非常繁琐.

import { ref, onBeforeMount, onMounted } from 'vue';

export default {

setup() {

const name = ref('Vue3');

onBeforeMount(() => {

console.log('onBeforeMount');

});

onMounted(() => {

console.log('onMounted'); // 获取后台数据

});

// 这里可以是对象,也可以返回个箭头函数

return {

name

};

}

}

```

```vue

// setup 语法糖写法

```

#### setup语法糖

如上边的代码写法,只要在script标签中写入一个:setup,就相当于写了一个setup()的方法,并且自动带有返回值。

> [!WARNING]

>

> 出现了一个问题,如果项目中的页面大量使用了name属性,则使用setup语法糖就需要写两个script标签了,应为setup语法糖没有了export default {} ,如果不想使用两个script标签,那么需要依赖插件进行处理,如下:

```v

// 安装

npm i vite-plugin-vue-setup-extend -D

```

```V

// 配置:vite.config.ts

import { defineConfig } from 'vite'

import vue from '@vitejs/plugin-vue'

import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({

plugins: [

vue(),

VueSetupExtend

],

})

```

```v

// 使用

```

### 生命周期

#### vue2 vs vue3

```

vue2 vs vue3

beforeCreate --------> setup(()=>{})

created --------> setup(()=>{})

beforeMount --------> onBeforeMount(()=>{})

mounted --------> onMounted(()=>{})

beforeUpdate --------> onBeforeUpdate(()=>{})

updated --------> onUpdated(()=>{})

beforeDestroy --------> onBeforeUnmount(()=>{})

destroyed --------> onUnmounted(()=>{})

activated --------> onActivated(()=>{})

deactivated --------> onDeactivated(()=>{})

```

#### 说明

> - setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method

>

> - onBeforeMount() : 组件挂载到节点上之前执行的函数;

>

> - onMounted() : 组件挂载完成后执行的函数;

>

> - onBeforeUpdate(): 组件更新之前执行的函数;

>

> - onUpdated(): 组件更新完成之后执行的函数;

>

> - onBeforeUnmount(): 组件卸载之前执行的函数;

>

> - onUnmounted(): 组件卸载完成后执行的函数;

>

> // 下边这俩是被包含在 中的组件多出的两个生命周期,与此同时onBeforeUnmount、onUnmounted就被下边的两个替换掉了,视情况使用

>

> - onActivated(): 被激活时执行;

>

> - onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;

[官方链接](cn.vuejs.org/api/composi…)

示例代码(vue3_test)中我进行了各个生命周期的演示打印

## 响应式数据API

**vue3中使用响应式API定义数据,ref与reactive是vue3中用来存储值的响应式数据源.**

### ref

ref函数一般用于创建简单数据类型的响应式数据,例如数字、字符串等。它包装了一个RefImpl的实现类,定义后返回一个包含value属性的对象。这个value属性是可变的,并且可以通过赋值改变其值。

```v

import { ref } from 'vue’;

const count = ref(0);

count.value++

```

​ 在script代码中改变定义的数据值,需要使用count.value = ? 来改变其值,否则无法实现双向数据绑定(count = ?不能实现双向绑定);

​ 展示在页面上不需要写value,例如直接展示这样写:{{ count }}即可.

### reactive

reactive函数则用于创建复杂数据类型的响应式数据,例如对象、数组等。它包装了一个代理proxy,会对传入的对象进行递归地响应式化处理,然后返回一个响应式代理对象。赋值的时候要直接赋值对象中的属性,如果直接赋值对象,那么无法实现双向数据绑定.

```v

import { reactive } from 'vue’;

const state = reactive({count:0})

state.count++

```

其实可以使用ref来定义对象与数组

### 响应式数据的特点

- **响应式** 就是A改变了,使用A的地方也会改变,

- **非响应式 **就是A改变了,使用A的地方并没有改变。

可以通过:ref(A),reactive(A),函数将普通变量、对象转换为响应式变量、对象,这样做可以使得对象内部的属性都是响应式的,即当它们的值发生改变时,视图会自动更新。

### ref 和reactive对比

宏观角度看:

> [!NOTE]

>

> 1、ref用来定义:基本类型数据、对象类型数据

>

> 2、reactive定义:对象类型数据

区别:

> [!NOTE]

>

> 1、ref创建的变量在js中必须使用.value(可以使用volar插件自动添加.value)

>

> 2、reactive重新分配一个新的对象,就会失去响应式(可以使用Object.assign去替换)

使用原则:

> [!IMPORTANT]

>

> 1、如果需要一个基本类型的响应式数据,必须使用ref

>

> 2、如果需要一个响应式对象,层级不深的,两者都可以使用

>

> 3、如果需要一个响应式对象,但是层级比较深,尽量使用reactive

**下边这张表格,做了一下深度对比:**

| reactive | ref |

| :------------------------------------- | ------------------------------------------------------------ |

| 只支持对象和数组(引用数据类型) | 支持基本数据类型 + 引用数据类型 |

| 在

| 重新分配一个新对象会丢失响应性 | 重新分配一个新对象不会失去响应 |

| 能直接访问属性 | 需要使用 .value 访问属性 |

| 将对象传入函数时,失去响应 | 传入函数时,不会失去响应 |

| 解构时会丢失响应性,需使用 toRefs | 解构对象时会丢失响应性,需使用 toRefs |

### 响应式丢失分析

- ref 定义数据(包括对象)时,都会变成 RefImpl(Ref 引用对象) 类的实例,无论是修改还是重新赋值都会调用 setter,都会经过 reactive 方法处理为响应式对象。

- 但是 reactive 定义数据(必须是对象),是直接调用 reactive 方法处理成响应式对象。如果重新赋值,就会丢失原来响应式对象的引用地址,变成一个新的引用地址,这个新的引用地址指向的对象是没有经过 reactive 方法处理的,所以是一个普通对象,而不是响应式对象。解构同理。

#### 一般情况解决

##### 逐行赋值(不推荐)

```v

```

##### 使用 Object.assign() (少数情况不适用)

```v

```

##### 使用ref()来进行定义(最简单)

```v

```

##### 直接在reactive中嵌套一层

```v

```

##### 如果有ts类型限制可以写类(TS对reactive里对象进行限制)******

单独拿出来一个ts文件,比如user.ts

```v

//1.定义限制userData的接口

export interface userInfo {

name: string,

age: number

}

//写类

export class data {

//定义userData并且做TS限制和赋初始值

userData: userInfo = {

name: "",

age: ""

}

}

```

在对应的.vue文件中引入该类

```v

// 1.引入刚写好ts类文件

import { userInfo, data } from "@/type/user.ts"

// 2.实例化出来data,然后用一个变量User接收。

let User = reactive(new data());

/*

//实例化出来以后相当于这样的结构:

User={

userData:{

name:"",

age:""

}

}

*/

// 3.获取接口数据

axios.get('/api/data')

.then(res => {

// 更新响应式变量的值

User.userData = res.data;//将返回的结果赋值给data,这样也不会丢失响应式,并且userData也受了TS的限制。

})

.catch(err => console.log(err));

```

#### 解构赋值解决

在Vue中,使用reactive定义变量时,需要注意解构赋值的情况。如果在解构赋值中使用reactive定义的变量,会导致数据丢失,因为解构赋值会创建一个新的引用,而不是原始对象。因此,我们应该避免在解构赋值中使用reactive定义的变量,或者使用拷贝或者toRefs(下边有介绍)来避免数据丢失。

```v

```

##### 直接访问reactive定义的变量,而不是使用解构赋值

##### 使用toRefs方法将响应式对象转化为普通对象的响应式属性

```v

```

##### 在解构赋值时使用拷贝来避免数据丢失

```v

```

### toRefs与toRef

#### toRefs(转换所有)

```v

// 定义响应式对象数据

let person = reactive({

name: '键盘上的梦',

age: 10

});

// 解构,后的值变成了ref定义的响应式数据

let { name, age } = toRefs(person);

// 打印,需要带.value

console.log(name.value, age.value)

```

#### toRef(转换指定)

```v

// 定义响应式对象数据

let person = reactive({

name: '键盘上的梦',

age: 10

});

// 使用toRef

let nl = toRef(person,'age')

// 打印

console.log(nl.value)

```

### computed计算属性

接受一个 getter 函数,返回一个只读的响应式 ref对象。该 ref 通过 .value 暴露 getter 函数的返回值。也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

```v

const count = ref(1)

const countNew = computed({

get: () => count.value + 1,

set: (val) => {

count.value = val - 1

}

})

countNew.value = 1

console.log(count.value) // 0

```

```javascript

const count = ref(1)

const countNew = computed(() => count.value + 1)

console.log(countNew) // 2

countNew.value++ // 错误

```

### watch监视

- 作用:监视数据的变化,和vue2中的watch作用一致

- 特点:vue3中的watch只能监视以下**四种数据**

> [!IMPORTANT]

>

> 1、ref定义的数据

>

> 2、reactive定义的数据

>

> 3、函数返回一个值(getter函数)

>

> 4、一个包含上述内容的数组

以下分别介绍上述4种情况

#### watch监视、一

监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变

> [!NOTE]

>

> watch的第一个参数是不需要写.value的,原因是看文档【监视的是定义的数据】,并不是值

```vue

```

#### watch监视、二

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监听对象内部的数据,需要手动开启深度监视。

> [!IMPORTANT]

>

> 若修改的ref定义的对象中的属性,newValue和oldValue都是新值,因为他们是同一个对象。

>

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

```vue

```

#### watch监视、三

- 监视reactive定义的【对象类型】数据,并默认开启了深度监视,而且不能关闭(隐式创建深度监听),就是deep默认是true。

- 监听的oldVlalue与newValue的值是一个,都是最新的

```vue

```

#### watch监视、四

监视ref或reactive定义的【对象类型】数据中的某个值,注意点如下:

1. 若该属性值不是【对象类型】,需要写成函数形式。

2. 若该属性值是依然是【对象类型】,可直接编,也可写成甘薯,不过建议写成函数。

```vue

```

> [!IMPORTANT]

>

> 结论:监视的钥匙对象里的属性,那么就直接写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监听。

#### watch监视、五

监听多个数据的变化,其实就是上述4种情况的汇总。

### watchEffect监视

立即运行一个函数,同时响应式的追踪其依赖,并在依赖更改时重新执行该函数。

```vue

.text-red { color: skyblue; }

```

watchEffect与watch的区别

> [!NOTE]

>

> 1、都能监听响应式数据的变化,不同的是监听数据变化的方式不同

>

> 2、watch:明确指出监听的数据

>

> 3、watchEffect:不明确指出监视的数据(函数中用到哪些属性,就监听哪些属性)

### 模版引用(ref)

#### 作用在html元素

获得的是这个元素

> [!WARNING]

>

> **在组件挂载后**才能访问模板引用

```vue

```

#### 作用在组件

获得的是这个组件的实例

```vue

```

下边的代码是子组件的源码,子组件中定义了3个属性,分别是a,b,c,如下

```vue

```

但是,在负组件中打印的ref的时候是查询不到子组件中的a,b,c三个属性的,原因是vue3的保护机制引起的,此处与vue2是不同的,vue2是父组件可以随意使用子组件的属性。

解决方式:子组件将数据暴露(使用defineExpose),暴露哪些,父组件就可以使用哪些,代码如下:

```vue

```

### 响应式数据的判断

- isRef: 检查一个值是否为一个 ref 对象

- isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理

- isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理

- isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理

## 接口-泛型-自定义类型

介绍接口、泛型、自定义类型,稍微详细的文档在typescript.Md文档中有介绍

类型的定义基本上有几种:

- 一种是在src目录下创建一个types的文件夹,所有约束全部写到这个文件夹里,我感觉这种方案适用于开源类的项目。

- 另一种是直接写到指定的组件与制定的页面目录下,定义个文件夹,在文件夹中创建ts文件,该文件只定义接口及类,element-plus-admin项目就是这么写的。

简单示例

```typescript

// types/index.ts

// 接口

export interface IPerson {

id: string;

name: string;

age: number;

}

// 自定义类型,范型

export type TPersons = Array;

// 与上边的一样,可以简写

export type TPersonList = IPerson[];

```

```vue

```

> [!NOTE]

>

> *`import { type IPerson, type TPersons } from '@/types';`*

>

> 导入这行代码中,只要是类型规范,前边必须加:**type**,否则会报错的,最新的规范就是强制要求

## 组件通信

介绍父子、子父、祖孙、兄弟组件间相互通信。

### Props(父-子)

`可以实现父到子组件组件间传值`

```vue

```

```vue

```

更多:[官方链接](cn.vuejs.org/guide/compo…)

### Props(子-父)

使用上述代码进行子传父的操作

父组件中引用子组件时,增加了`:getToy="getToy"`,并且增加了一个方法`getToy`

```vue

```

子组件,在defineProps中接收了父组件的传值:`getToy`,并通过一个方法postToy调用了这个传值,父组件将触发回调,获得value值,将value值赋值给父组件的toy,展示到页面

```vue

```

> [props传参总结]

>

> 1、父传子,很简单,通过子组件绑定参数,子组件使用defineProps接收即可

>

> 2、子传父,相当麻烦,父组件需要在子组件先绑定一个参数,这个参数其实是一个回调函数,子组件子组件使用defineProps接收,并通过触发方式将要传递的值赋值到父组件传递过来的函数后,父组件才能在回调函数中接收到子组件的参数。

### 自定义事件(子-父)

`可以实现子到父组件组件间传值`

```vue

```

```vue

```

### mitt(任意)

`可以实现任意组件间传值`,即:全局事件总线,与vue2中的Vue.prototype基本一致,另外与pubsub也是一致的,uniapp中的on,on,emit也是这个原理,vue3中没有了全局的this,因此需要使用三方库:mitt

[官网](www.npmjs.com/package/mit…)

**安装**

```shell

npm install mitt -save

```

**封装**(其实就是写一个工具类),创建个文件夹:mitt,里边写个index.ts文件

```typescript

import mitt from 'mitt'

const emitter = mitt();

export default emitter

```

还可以直接写成一个钩子(hooks),例如e element-plus-admin项目写的如下,钩子函数怎么用,会在hooks章节讲解。也是很简单的

```typescript

import mitt from 'mitt'

import { onBeforeUnmount } from 'vue'

interface Option {

name: string // 事件名称

callback: Fn // 回调

}

const emitter = mitt()

export const useEventBus = (option?: Option) => {

if (option) {

emitter.on(option.name, option.callback)

onBeforeUnmount(() => {

emitter.off(option.name)

})

}

return {

on: emitter.on,

off: emitter.off,

emit: emitter.emit,

all: emitter.all

}

}

// let {emit} = useEventBus()

// let {on} = useEventBus()

```

**使用**

```vue

// 发起页

```

```vue

// 接收页

```

**API**

> mitt() //创建mitt实例

>

> all //事件名称到注册处理程序函数的映射。

>

> emit(name,data) //触发事件,name:触发的方法名,data:需要传递的参数

>

> on(name,callback) //绑定事件,name:绑定的方法名,callback:触发后执行的回调函数

>

> off(name) //解绑事件,一个参数:name:需要解绑的方法名

### v-model(父-子,子-父)

用在html标签上,是双向数据绑定,用在组件上,就可以用来传值,基本上大部分UI库上全是这种写法。

假设我们要写一个组件,``

首先我们应该了解到原生的input组件是可以使用v-model进行双向绑定的,因此我们自己做组件使用v-model做双向数据绑定,就需要了解input的v-modle原理,如下:

```vue

// input的原理,其实就是使用modelValue绑定数据值,再用update:modelValue更新数据值

// 因此我们需要去组件中,处理modelValue值的传递(父传子),与update:modelValue函数的触发(子传父)

<HtInput :modelValue="username" @update:modelValue="username = $event" />

```

组件可以这么写

```vue

input { border: 1px solid skyblue; border-radius: 3px; height: 35px; line-height: 35px; color: coral; } input:focus { outline: 1px solid #000; }

```

### $attrs(祖-孙)

概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖->孙)

说明:$attrs是一个对象,包含所有父组件传入的标签属性。

> 注意:$attrs会自动排除props中声明的属性(可以理解成,通过props声明过的被子组件自己“消费”掉了)

```vue

// 祖父

```

```vue

// 父亲:只绑定了$attrs到子组件上,其它事都没做

```

```vue

// 孙组件

```

> 注意:

>

> 这种祖孙间的传值,有一个弊端,就是必须通过中间人(父亲)做了一件事:`v-bind="$attrs"`

### $refs(父-子)

`值为对象,包含所有被ref属性标识的DOM元素或组件实例。`

```vue

// 父组件

```

```vue

// 子组件1

```

```vue

// 子组件2

```

### $parent(子-父)

`值为对象,当前组件的父组件实例对象。`

```vue

// 子组件

```

```vue

// 父组件

```

### provide&inject(祖-孙)

注入依赖:[官方链接](cn.vuejs.org/guide/compo…)

- 作用:实现祖与后代组件间通信

- 套路:父组件有一个 `provide` 选项来提供数据,后代组件有一个 `inject` 选项来开始使用这些数据

```vue

// 父组件

```

```vue

// 子组件,什么都没做

```

```vue

// 孙组件

```

# 插槽

## 默认插槽

```vue

// 父组件

h1 { color: blue; }

```

```vue

// 子组件

.contianer { background-color: skyblue; margin: 10px 0; box-shadow: 0 0 5px; padding: 10px; }

```

## 具名插槽

```vue

// 父组件

h1 { color: blue; }

```

```vue

// 子组件

.contianer { background-color: skyblue; margin: 10px 0; box-shadow: 0 0 5px; padding: 10px; }

```

> [!NOTE]

>

> 总结:其实具名插槽与默认插槽是一样的,只不过默认插槽的名称叫:defalut, vue将它简化了

## 作用域插槽

- 作用域插槽就是可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传数据是以何种结构或外观在子组件内部去展示!!!

```vue

// 父组件

h1 { color: blue; }

```

```vue

// 子组件

.contianer { background-color: skyblue; margin: 10px 0; box-shadow: 0 0 5px; padding: 10px; }

```

# 自定义hooks

- 什么是hook? 本质是一个函数,把setup函数中使用的Composition API进行了封装。

- 类似于vue2.x中的mixin。

- 自定义hook的优势:复用代码, 让setup中的逻辑更清楚易懂。

示例代码:

```vue

// 传统写法

.contianer { background-color: skyblue; margin: 10px 0; box-shadow: 0 0 5px; padding: 10px; } img { width: 80px; margin-right: 5px; }

```

自定义钩子写法

自定两个钩子函数

```typescript

// sum hooks

import { ref, onMounted } from 'vue';

export default function () {

//数据

let sum = ref(0);

// 方法

function add() {

sum.value += 10;

}

// 甚至可以写钩子函数

onMounted(() => {

setTimeout(() => {

add();

}, 2000);

});

return { sum, add };

}

```

```typescript

// dog hooks

import { ref, reactive } from 'vue';

import axios from 'axios';

// dog.ceo/api/breed/p…

export default function () {

//数据

let dogList = reactive(['images.dog.ceo/breeds/pemb…]);

// 方法

async function getDog() {

try {

let result = await axios.get(' dog.ceo/api/breed/p…');

dogList.push(result.data.message);

} catch (error) {

alert(error);

}

}

// 暴露

return { dogList, getDog };

}

```

新的页面

```vue

// 使用hooks

.contianer { background-color: skyblue; margin: 10px 0; box-shadow: 0 0 5px; padding: 10px; } img { width: 80px; margin-right: 5px; }

```

# Tsx/Jsx

总结了一句话:`TSX = TS + JSX`,就是在原来的JSX中添加了TS的类型规范,使用方式上没有区别。

# 动态组件

有些场景会需要在两个组件间来回切换,比如 Tab 界面,tab页在切换的时候根据条件的不同,切换不同的组件。

使用vue内置的组件,专门用来实现动态组件渲染功能。在使用时,需要给它传入一个is属性,is绑定的是一个组件的名称,说白了,其实就是代表的一个动态的占位符,通过is属性来动态绑定组件。

## 使用

```vue

```

> [!IMPORTANT]

>

> 对于动态组件,Vue会在组件切换时销毁前一个组件实例并创建新实例。所以切换动态组件时不会保留组件的状态。通过vuejs devtools我们可以清晰的看到,每当切换到当前标签的时候上一个标签组件都会销毁。如下图,从【我是组件1】切换到【我是组件2】,【我是组件1】的子组件已经销毁了,只保留了【我是组件2】Person2子组件。

那么如果想要保留这些子组件需要怎么办呢?就需要使用keep-alive了。

## keep-alive

### **什么是keep-alive**

keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。keep-alive 是一个抽象组件: 它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。实际应用中主要是将组建缓存到内存中,避免切换路由的时候页面刷新从而倒是DOM的重新渲染。

### 生命周期

- onActivated 当组件被激活(使用)的时候触发,可以简单理解为进入这个页面的时候触发;

- onDeactivated 当组件不被使用的时候触发,可以简单理解为离开这个页面的时候触发。

### keep-alive属性

- include : 只有匹配的组件会被缓存;

- exclude : 任何匹配的组件都不会被缓存;

- max : 最多可以缓存多少组件实例。

### 动态组件使用

在动态组件栏目中介绍过,当标签切换时会销毁上一个标签内引用的组件,那么会导致在每次切换tab的时候,子组件会重新渲染页面,从而触发created,mounted等钩子函数,用户体验不好。我们在多级菜单+表单验证,列表页+详情页等场景中是不需要重新渲染的,所以就需要使用keep-alive对组件进行缓存。

```vue

// 使用keep-alive包裹动态组件,使用vuedevtools查看

```

# 新的组件

## 1.Fragment

- 在Vue2中: 组件必须有一个根标签

- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

- 好处: 减少标签层级, 减小内存占用

## 2.Teleport

- 什么是Teleport?—— `Teleport` 是一种能够将我们的组件html结构移动到指定位置的技术。

```vue

我是一个弹窗

<button @click="isShow = false">关闭弹窗

```

## 3.Suspense

- 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

使用步骤:

- 异步引入组件

```vue

import {defineAsyncComponent} from 'vue'

const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

```

- 使用```Suspense```包裹组件,并配置好```default``` 与 ```fallback```

```vue

```

# vite

这个自己查看一下官方网站吧,都是中文的,这个东西是项目最重要的东西,没有它项目不能运行,不能打包。如果webpack有深入理解,那么vite会相对来说简单一些。

# 路由(vue-router)

## 配置vue-router

**新建** **router** **文件夹**

**路径:****/src/router**

**新建** **index.ts** **文件**

**路径:****/src/router/index.ts**

**index.js**

```vue

import {createRouter,createWebHistory } from 'vue-router'

const routes = [

{

path:'/',

component:() => import('../pags/login.vue')

}

]

const router = createRouter({

history:createWebHistory(), // 路由history模式

routes //路由信息

})

export default router

```

**main.ts**

```vue

import { createApp } from 'vue'

import App from './App.vue'

import router from './router'

createApp(App).use(router).mount('#app')

```

**App.vue**

```vue

```

## 命名路由-编程式导航(跳转)

除了 path 之外,你还可以为任何路由提供 name。

```vue

const routes:Array = [

{

path:"/",

name:"Login",

component:()=> import('../components/login.vue')

},

{

path:"/reg",

name:"Reg",

component:()=> import('../components/reg.vue')

}

]

```

[router-link]跳转方式需要改变 变为对象并且有对应name

```vue

Login

Reg


```

### 编程式导航

除了使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

### **1.字符串模式**

```vue

import { useRouter } from 'vue-router'

const router = useRouter()

const toPage = () => {

router.push('/reg')

}

```

### **2.对象模式**

```vue

import { useRouter } from 'vue-router'

const router = useRouter()

const toPage = () => {

router.push({

path: '/reg'

})

}

```

### **3.命名式路由模式**

```vue

import { useRouter } from 'vue-router'

const router = useRouter()

const toPage = () => {

router.push({

name: 'Reg'

})

}

```

## **路由传参**

### **useRouter与useRoute的区别**

**useRouter是全局对象,用来传入参数。useRoute是当前对象,用来接收参数**

### Params路由传参

编程式导航 使用router.push 或者 replace 的时候 改为对象形式并且只能使用name,path无效,然后传入params

**使用****useRouter****传入参数(params)**

```vue

```

**使用****useRoute****接收参数(params)**

```vue

ID:{{ route.params?.id }}

import { useRoute } from 'vue-router';

const route = useRoute()

```

**动态路由传参**

很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数 。

路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件

**因为用params路由传参,刷新界面会失去参数。配置动态路由之后就刷新后依然存在。**

```vue

{

path:'/listFirst',

name:'listFirst',

component:()=>import('../pages/transmitInfo/list-first')

},

{

path:'/listSecond/:id/:tips',//配置动态路由参数 :id、:tips

name:'listSecond',

component:()=>import('../pages/transmitInfo/list-second'),

}

```

### Query路由传参

编程式导航 使用router push 或者 replace 的时候 改为对象形式新增**query 必须传入一个****对象**

**使用****useRouter****传入参数(params)**

```vue

const router = useRouter();

const handlerId =(id,tips) =>{

router.push({path:'/listSecond',query:{id,tips}})

}

```

**使用****useRoute****接收参数(params)**

```vue

ID:{{ route.query?.id }}

import { useRoute } from 'vue-router';

const route = useRoute()

```

**二者的区别**

1. query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效

2. query 在路由配置不需要设置动态路由参数,而 params 必须设置

3. query 传递的参数会显示在地址栏中

4. params传参刷新会无效(设置动态路由参数后有效),但是 query 会保存传递过来的值,刷新不变 ;

5. 路由配置

### Vue-router3 与4的区别

- **安装和配置**:Vue Router 3和Vue Router 4的安装方式不同,分别适用于Vue 2和Vue 3。Vue Router 3是为Vue 2设计的,而Vue Router 4是为Vue 3设计的。在配置路由时,Vue Router 3使用`Vue.use(Router)`来安装路由,并在组件中使用`this.routerˋtˋhis.router\`和\`this.route`来访问路由实例和路由对象。相比之下,Vue Router 4使用`createRouter`函数来创建路由实例,并通过`useRouter`和`useRoute`钩子在组件中获取路由实例和路由对象。

- **使用方式**:在Vue Router 3中,组件通过`this.router访ˋ问路由实例,通过tˋhis.router\`访问路由实例,通过\`this.route`访问当前路由信息。而在Vue Router 4中,由于使用了Composition API,组件通过`useRouter`和`useRoute`函数来获取路由实例和路由对象。此外,Vue Router 4引入了新的导航守卫,如`onBeforeRouteUpdate`和`onBeforeRouteLeave`,以适应Composition API的使用方式。

- **API变更**:Vue Router 4对API进行了更新,以适应Vue 3的Composition API。例如,Vue Router 3中的`beforeRouteUpdate`和`beforeRouteLeave`守卫被替换为Vue Router 4中的`onBeforeRouteUpdate`和`onBeforeRouteLeave`。

# axios(请求库)

基于Promise的网络请求库

什么是Promise(前提)

配置文件

请求拦截器

响应拦截器

# pinia(状态管理器)

集中式状态管理容器,实现任意组件通信

pinia状态管理器, 是vue2的中常用的 vuex 的升级版 vuex5 , 也叫大菠萝, 保留了state, getter 和 action, 没有了mutations.

优点是完整的typescript(TS)支持等,其它优点自行百度查询.

## **选项式写法(vue2写法)**

1、安装

npm install pinia

2、创建store

根目录创建store文件夹,store下创建index.ts,另外再创建一个moudles文件夹,作为模块文件夹;

创建大仓库,在store文件夹下创建index.ts

```vue

import { createPinia } from 'pinia'

// createPinia()方法用于创建仓库;

const store = createPinia()

export default store;

```

创建小仓库,在store文件夹下创建modules文件夹,用来管理小仓库,在里边创建个小仓库的ts文件,例如:info.ts

```jsx

// 定义info小仓库

import { defineStore } from "pinia";

// 第一个参数info: 小仓库的名字;第二个参数: 小仓库的配置对象

// defineStore 方法执行后返回的是一个函数, 该函数的作用就是让组件可以获取到小仓库的数据

export const useInfoStore = defineStore('info', {

// state是函数,而原vuex是对象,这里有区别

state: () => {

return {

count: 99

}

},

getters: {},

actions: {}

})

```

3、挂载store,在main.ts中进行挂载

```jsx

//引入仓库

import store from './store'

//使用store

app.use(store)

```

4、使用store,在页面中使用,例如在userInfo.vue中使用

```vue

```

**注意: 可以使用ES传统方式解构: count, 是能获取到值的,但是不具有响应性;**

**可以使用****storeToRefs进行解构,后可以直接使用count,如下2行代码即可**

**import { storeToRefs } from "pinia";**

**const { count } = storeToRefs(store);**

5、修改store数据

```vue

```

与之对应的info.ts文件中增加updateCount()方法

```jsx

// 定义info小仓库

import { defineStore } from "pinia";

// 第一个参数info: 小仓库的名字;第二个参数: 小仓库的配置对象

// defineStore 方法执行后返回的是一个函数, 该函数的作用就是让组件可以获取到小仓库的数据

export const useInfoStore = defineStore('info', {

// state是函数,而原vuex是对象,这里有区别

state: () => {

return {

count: 99

}

},

getters: {},

//没有了mutations、commit去修改数据,只有actions了

actions: {

//原vuex中这里是有context上下文对象的,但是pinia中是没有的

//**** pinia这里有一个小仓库对象,可以直接使用,即this,例如下边的函数方法修改count的值

// 函数是可以接受任何值的

updateCount(){

this.count++

}

}

})

```

6、计算属性getters

```jsx

// 定义info小仓库

import { defineStore } from "pinia";

// 第一个参数info: 小仓库的名字;第二个参数: 小仓库的配置对象

// defineStore 方法执行后返回的是一个函数, 该函数的作用就是让组件可以获取到小仓库的数据

export const useInfoStore = defineStore('info', {

// state是函数,而原vuex是对象,这里有区别

state: () => {

return {

count: 99,

arr: [1,2,3,4,5,6,7,8,9,10]

}

},

getters: {

// 计算arr数组的总和

total(){

let result = this.arr.reduce((prev, next) => {

return prev + next

},0)

return result

}

},

//没有了mutations、commit去修改数据,只有actions了

actions: {

//原vuex中这里是有context上下文对象的,但是pinia中是没有的

//**** pinia这里有一个小仓库对象,可以直接使用,即this,例如下边的函数方法修改count的值

// 函数是可以接受任何值的

updateCount(){

this.count++

}

}

})

```

## 组合式写法(vue3写法)

1、安装

npm install pinia

2、创建store

创建小仓库,在store文件夹下创建modules文件夹,用来管理小仓库,在里边创建个小仓库的ts文件,例如:todo.ts

```jsx

// 定义info小仓库

import { defineStore } from "pinia";

import { ref,computed } from "vue";

// 这里与选择式有点区别,第二个参数是一个函数,该函数务必返回一个对象,对象中包含属性与方法,供组件使用

export const useTodoStore = defineStore('todo', () => {

let todos = ref([{"id":1,"title":"吃饭"},{"id":2,"title":"睡觉"},{"id":3,"title":"打豆豆"}]);

let arr = ref([1,2,3,4,5,6]) //计算属性使用数组getters

const total = computed(()=>{

return arr.value.reduce((prev, next) => {

return prev + next

},0)

})

return {

todos,

total,//计算属性

updateTodo(){

todos.push({"id":4,"title": "我是push进来的"})

}

}

})

```

# unocss

## 简介

- UnoCSS 是一个即时、按需的原子级 CSS 引擎。它专注于提供轻量化、高性能的 CSS 解决方案。

- “Instant On-demand” 表示 UnoCSS 的加载和渲染速度非常快,可以立即进行使用。它不需要预先编译,使得样式可以在需要时动态地生成。

- “Atomic CSS Engine” 表示 UnoCSS 使用原子级 CSS 样式的概念。原子级 CSS 是一种通过将样式属性定义为独立的类来构建页面的方法。每个类都只包含一项或几项样式属性,可以在组件中灵活地组合和应用这些类,以实现细粒度的样式控制。

- UnoCSS 的目标是通过仅传递实际使用的样式属性,减小生成的 CSS 文件的体积。这样可以优化页面的加载速度,并减少不必要的网络传输和运行时的样式计算。

- 总的来说,UnoCSS 是一个即时、按需的原子级 CSS 引擎,旨在提供快速、高性能的原子级 CSS 解决方案。

## 应用

对应刚入手 `unocss` 的同事来说,规则怎么写是不知道的,但不用着急,直接打开官方提供的[在线交互文档](unocss.dev/interactive…

# 其它

1、vscode自动为ref定义的变量加.value

vscode 左下角【设置】,搜:Dot value,打勾,重启vscode,就可以了

2、vue3将全局的API,即:```Vue.xxx```调整到应用实例(```app```)上

| 2.x 全局 API(```Vue```) | 3.x 实例 API (`app`) |

| ------------------------- | ------------------------------------------- |

| Vue.config.xxxx | app.config.xxxx |

| Vue.config.productionTip | 移除 |

| Vue.component | app.component |

| Vue.directive | app.directive |

| Vue.mixin | app.mixin |

| Vue.use | app.use |

| Vue.prototype | app.config.globalProperties |

3、移除过滤器(filter)

> ***引用官方说法***

>

> 过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

# 补充

## setup语法糖新特性

3.3 + 版本新增的宏命令

### defineOptions()

==vue3.3版本新增宏命令==⬇️

这个宏可以用来直接在 `

```vue

```

- 仅支持 Vue 3.3+。

- 这是一个宏定义,选项将会被提升到模块作用域中,无法访问 `

### defineModel()

#### 用法及实现

==vue3.4版本新增宏命令== ⬇️

在vue3.4之前,如果在组件上使用v-model进行传值,那是很麻烦的,在上文中【组件通信】-【v-model】里有介绍,实际就是给组件定义了`modelValue`属性和监听`update:modelValue`事件,所以我们以前要实现数据双向绑定需要给子组件定义一个`modelValue`属性,并且在子组件内要更新`modelValue`值时需要`emit`出去一个`update:modelValue`事件,将新的值作为第二个字段传出去。如下:

```vue

// 父组件,只是举例,代码

```

```vue

// 子组件

```

使用`defineModel`宏之后,要简单很多了,如下:

```vue

// 父组件,没变

```

```vue

// 子组件,简单了很多哈

```

==附:实现原理==

`defineModel`其实就是在子组件内定义了一个叫`model`的ref变量和`modelValue`的props,并且`watch`了props中的`modelValue`。当`props`中的`modelValue`的值改变后会同步更新`model`变量的值。并且当在子组件内改变`model`变量的值后会抛出`update:modelValue`事件,父组件收到这个事件后就会更新父组件中对应的变量值。

```vue

// 子组件,实现原理

```

#### 定义类型与默认值

```vue

const model = defineModel({ type: String, default: "20" });

```