Vue笔记

基础篇

Vue3简介

API的区别

vue2使用的选项式API,选项式API以组件不同方面(数据、方法、生命周期钩子)以选项的形式进行组织。

vue3使用的组合式API,允许开发者以函数的形式组织逻辑,将相关逻辑组合在一起

Vue3相比Vue2的好处

性能提升

​ 打包大小减少

​ 初次渲染更快,更新渲染快

​ 内存减少

源码的升级

​ 使用Proxy代替defineProperty实现响应式

​ 重写虚拟DOM实现和Tree-Shaking。

拥抱TypeScript

​ Vue3可以更好地支持TypeScript

新特性

Composition Api(组合API):

​ setup

​ ref与reactive

​ computed与watch

新的内置组件:

​ Fragment

​ Teleport

​ Suspense

其他改变

​ 新的生命周期钩子

​ data选项应始终被声明为一个函数

​ 移除keyCode支持作为v-on的修饰符

创建Vue3工程

基于vue-cli(不推荐)

​ 创建方式:

​ vue –version

​ npm install -g @vue/cli

​ vue create vue_test

基于vite创建(推荐)

优势:

​ 轻量加速热重载(HMR),能实现极速的服务启动

​ 对TypeScript、JSX、CSS等支持开箱即用

​ 真正的按需编译,不再等待整个应用编译完成

​ webpack构建与vite构建的对比

​ webpack:

​ 启动先执行路由再执行模块,确认都执行完毕没问题再启动

​ vite:

​ 启动之后就已经启动成功了,然后再去看你正在看的的哪个路由随后再加载对应的模块

创建命令:

​ npm create vue@latest

配置项目:

​ 配置项目名称

​ Project name:(起英文名)

​ 是否添加TypeScrupt支持

​ Add typeScript? Yes

​ 是否添加JSX支持

​ Add JSX Support? No

​ 是否添加路由环境

​ Add Vue Router For Single Page Application developent? No

​ 是否添加pinia环境

​ Add Pinia for state management? No

​ 是否添加单元测试

​ Add Vitest for Unit Testing? No

​ 是否添加端到端测试方案

​ Add an End-to-End Testing Solution? No

​ 是否添加ESLint语法检查

​ Add ESLint for code quality? Yes

​ 是否添加Preettiert代码格式化

​ Add Prettier for code formatting? No

总结:

​ Vite项目中,index.html是项目的入口文件,在项目最外层。

​ 加载index.html后,Vite解析type=”module”指向的JavaScript

​ Vue3中是通过createApp函数创建一个应用实例

Vue3也可以使用Vue2的语法

OptionsAPI与CompositionAPI

​ Vue2的API风格是Options(配置)风格的

​ Vue3的API风格是Composition(组合)风格的

OptionsAPI的弊端

​ Options类型的API,数据、方法、计算属性等,是分散在:data,methods,computed中的,若想新增或者修改一个需求,就需要分别修改:data,methods,computed,不便于维护和复用

CompositionAPI的优势

​ 可以用函数的方法,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起

setup

setup概述

setup是Vue3中一个新的配置项,值是一个函数,它是Composition API“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视……等等,均配置在setup中

特点如下:

​ setup函数返回的对象中的内容,可直接在模板中使用。

​ setup中访问this是undefined。

​ setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的

setup与OptionsAPI的关系

setup和OptionsAPI语法可以共存,OptionsAPI可以this到setup中的数据,但是setup里拿不到vue旧语法的值

setup导出

在vue文件中,export导出不能和setup共用一个script,但是可以使用

并且安装npm i vite-plugin-vue-setup-extend -D此命令库

在vite.config.ts配置:

​ import VueSetupExtend from ‘vite-plugin-vue-setup-extend’
​ plugins: [
VueSetupExtend()
​ ],

这样舍弃了export繁琐的代码导出,直接在sript标签上添加name即可使用

响应式数据

ref创建:基本类型的响应式数据

作用:定义响应式变量

ref可以定义基本类型、对象类型的响应式数据

ref包裹对象类型的响应式数据,实际是value是用的reactive返回的proxy的实例对象

在setup中

import {ref} from ‘vue’

	let age = ref(18);

​ function changeAge() {
​ age.value += 1;
​ }

原本在setup中的数据,即便修改也没有在页面中响应,但是将setup中需要修改的数据加上ref后,ref会将这个属性变成一个对象,调用的方法就可以属性.value对它作出修改就会在页面进行响应,在模板字符串中调用这个属性的时候只用填写这个的属性名,自然就会将对象里的这个属性值拿出来了。

age不是响应式,但是age.value是响应式

vue2直接写在选项式API里自然就进行响应了

reactive创建

作用:定义响应式变量

reactive只能定义对象类型的响应式数据,返回一个Proxy的实例对象,

reactive定义的响应式是深层次的

在setup中:

​ import { reactive } from “vue”;

​ let car = reactive({ brand: “奔驰”, price: 100 });

​ function changePrice() {
car.price += 10;

​ }

和ref一样不过reacive是针对负责复杂数据类型的响应式而创建的,而上述代码中的car是proxy代理对象

toRefs和toRef

toRefs

作用:将一个reactive对象里的属性,值都变成ref对象,并形成一个新的对象,这时候只用解构对象属性就可以拿到带有响应式的属性了

在setup中

let person = reactive({

name: ‘张三’,

age: 18

})

let {name, age} = person

解构后的name,age并不是响应式,只是拿到了person里的对象属性,但如果是person.name,person.age那么就是拿到了响应式的对象属性

toRef

作用:将一个reactive对象中的属性用toRef包裹起来,这个时候会形成一个新的对象,属性和值都变成ref对象,对于这个新对象直接调用就可以使用了

let person = reactive({

name: “张三”,

age: 18,

});

let nl = toRef(person, “name”);

console.log(nl.value);

computed计算属性

特点:当依赖的数据发生变化时会重新计算

如果当要一直调用一个方法来放到页面中,尽量使用计算属性,如果使用方法来放到页面中,那么调用几次方法就会显示被调用多少次,但是如果用计算属性,那么他的缓存会让他调用多少次都只会被调用一次

计算属性返回的值也是Ref是ComputedRefImpl,不过是只读的没有办法对value进行修改

  • 对象解构:使用花括号 {} 来定义解构模式,变量名需要与对象的属性名相匹配(可以使用别名改变变量名)。

  • 数组解构:使用方括号 [] 来定义解构模式,变量的顺序与数组元素的顺序相对应。

let firstName = ref(“zhang”);

let lastName = ref(“san”);

let fullName = computed({

get() {

return (

firstName.value.slice(0, 1).toUpperCase() +

firstName.value.slice(1) +

“~” +

lastName.value

);

},

set(val) {

const [str1, str2] = val.split(“-“);

firstName.value = str1;

lastName.value = str2;

},

});

function changeFullName() {

fullName.value = “li-si”;

}

watch监听数据

​ 作用:监视数据的变化

​ 特点:Vue3中的watch只能监视一下四种数据

​ 1.ref定义的数据。

​ 2.reactive定义的数据。

​ 3.函数返回一个值(getter函数)

​ 4.一个包含上述内容的数组

​ 第一个参数是侦听器的源, 这个来源可以是一下几种:

​ 一个函数、一个值(getter函数)

​ 一个ref

​ 一个响应式对象

​ …或者是由以上类型的值组成的数组

情况一

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

​ import { ref, computed, watch } from “vue”;

let sum = ref(0);

function changeSum() {

sum.value += 1;

}

const stopWatch = watch(sum, (newValue, oldValue) => {

console.log(“sum变化了”, newValue, oldValue);

if(newValue >= 10) {

stopWatch()

}

});

情况二

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

​ 如果修改ref定义的【对象类型】数据的单个属性,那么watch深度监听到的新旧属性都是一样的,因为地址都一样,如果不开启watch深度监听那么单个修改对象类型的数据都不会监听到,但如果给对象类型的数据,换一个新的对象类型的数据,因为地址的改变,那么新旧值就是不一样的

​ import { ref, watch } from “vue”;

let person = ref({

name: “张三”,

age: 18,

});

function changeAge() {

person.value.name += ‘~’

}

function changeName() {

person.value.age += 1

}

function changePerson() {

person.value = {

name: ‘李四’,

age: 90

}

}

watch(person, (newValue, oldValue) => {

console.log(newValue,oldValue);

}, {deep: true,immediate: true})

情况三

​ 监听reactive定义的【对象类型】数据,且默认开启了深度监视,且不可关闭(隐式创建深层监听)

​ reactive定义的对象不可整体修改

​ 可以使用Object.assign(person,{name:’李四’, age: 80})浅拷贝

​ import { reactive, watch } from “vue”;

let person = reactive({

name: “张三”,

age: 18,

});

let obj = reactive({

a: {

b: {

c: 666,

},

},

});

function changeAge() {

person.name += “~”;

}

function changeName() {

person.age += 1;

}

function changePerson() {

return Object.assign(person, {

name: “李四”,

age: 90,

});

}

function test() {

obj.a.b.c = 888

}

watch(person, (newValue, oldValue) => {

console.log(“person变化了”, newValue, oldValue);

});

watch(obj, (newValue,oldValue) => {

console.log(‘obj变化了’, newValue,oldValue);

})

情况四

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

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

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

​ import { reactive, watch } from “vue”;

let person = reactive({

name: “张三”,

age: 18,

car: {

car1: “奔驰”,

car2: “宝马”,

},

});

function changeName() {

person.name = “李四”;

}

function changeAge() {

person.age += 1;

}

function changeC1() {

person.car.car1 = “奥迪”;

}

function changeC2() {

person.car.car2 = “大众”;

}

function changeCar() {

person.car = { car1: “雅迪”, car2: “兰博” };

}

watch(

() => {

return person.name;

},

(newValue, oldValue) => {

console.log(“监视了”);

}

);

watch(

() => {

return person.car

},

(newValue, oldValue) => {

console.log(newValue, oldValue);

},{deep: true}

);

如果没加函数,那么监听不到person.car的整体对象,只能监听到person.car的属性,如果加了就只能监听到整体对象,监听不到单个属性,但是最后加深度监听便都可以监视了

情况五

​ 监听上述的多个数据,只用将watch参数变成数组,以及要处理的对象属性即可

​ watch([

() => person.car

,() => person.name ]

,

(newValue, oldValue) => {

console.log(newValue, oldValue);

}, {

deep:true

}

);

watchEffct

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

​ watch对比watchEffct

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

​ watch:要明确指出监视的数据

​ watchEffectL不同明确指出监视的数据(函数中监视哪些属性,那就用到哪些属性)

​ 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([temp, height],(Value) => {

// let [tempValue, HeightValue] = Value;

// if(tempValue >= 60 || HeightValue >=80) {

// console.log(‘请求’);

// }

// })

watchEffect(() => {

if(temp.value >= 60 || height.value >=80) {

console.log(‘请求’);

}

})

标签里的Ref属性

创建一个title2,用于存储ref标记的内容

h2 ref=”title2” 北京 h2

let title2 = ref()

如果父页面和子页面某个元素都有相同的id选择器名字,那么会冲突,第二个id会被第一个id选择器覆盖,但用ref的时候就不会冲突

如果将ref放在组件标签里,那么就会得到组件实例proxy代理对象,组件实例里面的内容都被保护起来了,所以什么都看不到,只要将子组件里想暴露出来的数据就使用

defineExpose({a, b, c})

TS中的接口、泛型、自定义类型

接口演示

TS

// 定义一个接口,用于限制person对象的具体属性

export interface PersonInter {

id: string,

name: string,

age: number

}

vue

import { type PersonInter, type PersonType } from “@/types”;

let person: PersonInter = { id: “123”, name: “张三”, age: 60 };

泛型

let personList: Array(尖括号)PersonInter(尖括号) = [

{ id: “1”, name: “张三”, age: 60 },

{ id: “2”, name: “张三”, age: 60 },

{ id: “3”, name: “张三”, age: 60 },

];

let personList = reactive(尖括号)PersonType(尖括号)([

{ id: “1”, name: “张三”, age: 60 },

{ id: “2”, name: “张三”, age: 60 },

{ id: “3”, name: “张三”, age: 60 },

]);

自定义类型

TS

// 一个自定义类型

export type PersonType = Array(尖括号)PersonInter(尖括号);

let personList: PersonType= [

{ id: “1”, name: “张三”, age: 60 },

{ id: “2”, name: “张三”, age: 60 },

{ id: “3”, name: “张三”, age: 60 },

];

Props

​ 使用方法:

​ 在父组件中的子组件标签中起一个属性名字,在里面写入想传入的值,如果是变量,那么就将前面加个冒号,后面将变量传进去。在子组件中用defineProps接收

​ 父组件:

​ Person a=”哈哈” />

​ 子组件:

​ import {defineProps} from ‘vue’

​ let x = defineProps([‘a’])

​ console.log(x.a);

						也可以做限制类型

​ defineProps<{list: PersonType}>()

​ 也可以做可传可不传的限制

​ defineProps<{list?: PersonType}>()

​ 最终极的写法,限制必要性,写默认值(withDefaults)

​ withDefaults(defineProps<{ list?: PersonType }>(), {

list: () => [{ id: “1”, name: “李四”, age: 18 }],

});

对生命周期的理解

组件的生命周期

​ 创建、挂载、更新、销毁

Vue2的生命周期

​ 十一个生命周期,组件生命周期四个阶段,八个周期

​ // 创建前钩子函数

beforeCreate() {

	console.log('beforeCreate')

},

  • 触发时机:在实例初始化之后,数据观测 (data 属性) 和 event/watcher 事件配置之前被调用。

    // 创建后钩子函数

    created() {

​ console.log(‘created’)

},

  • 触发时机:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 (data 属性)、propertymethod 的计算、watch/event 事件回调的配置等。然而,挂载阶段还没有开始,$el 属性目前不可用。

    // 挂载前钩子函数

    beforeMount() {

​ console.log(‘beforeMount’)

},

  • 触发时机:在挂载开始之前被调用,此时模板编译成 render 函数完成,但还未将 render 函数返回的虚拟 DOM 挂载到页面上。

    // 挂载后钩子函数

    mounted() {

​ console.log(‘mounted’)

},

  • 触发时机:在挂载完成后调用,此时 $el 已经被创建并替换掉 el 选项所指向的 DOM 元素。

    // 更新前钩子函数

    beforeUpdate() {

​ console.log(‘beforeUpdate’)

},

  • 触发时机:在数据更新之前被调用,发生在虚拟 DOM 打补丁之前。

    // 更新后钩子函数

    updated() {

​ console.log(‘updated’)

},

  • 触发时机:在数据更新之后被调用,发生在虚拟 DOM 打补丁之后。

    // 销毁前钩子函数

    beforeDestroy() {

​ console.log(‘beforeDestroy’)

},

  • 触发时机:在实例销毁之前调用。此时实例仍然完全可用。

    // 销毁后钩子函数

    destroyed() {

​ console.log(‘destroyed’)

}

  • 触发时机:在实例销毁之后调用。此时所有的事件监听器和子实例都已经被销毁。

Vue3的生命周期

​ 创建阶段: setup

​ 挂载阶段:onBeforeMount、onMounted

​ 更新阶段:onBeforeUpdate、onUpdate

​ 卸载阶段:onBeforeUnmount、onUnmounted

​ 常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

​ 父组件与子组件钩子关系

​ 父创建,父挂载前,子创建,子挂载前,子挂载后,父挂载后

自定义Hooks

​ 正因为有了Hooks,这个组合式Api才有意义

好处:

逻辑复用与代码组织

​ 可复用逻辑封装

​ 逻辑关注点分离

提高代码可读性和可维护性

​ 代码模块化

​ 减少模板代码

更好的状态管理和响应式处理

​ 响应式逻辑封装

​ 状态管理的灵活性

提升开发效率和团队协作

​ 快速迭代和开发

​ 团队协作

用法

​ 将组件里的功能拆分到hooks文件夹下,一个功能对应一个,并用默认导出,将数据与方法返回。让想调用此功能的组件import导入功能,再将其数据方法解构,即可使用

路由

路由的理解

​ router是路由器,route是路由

​ 路由就是一组key-value的对应关系

​ 多个路由,需要经过路由器的管理

​ 比如:点击菜单栏路径发生变化,就会被路由器监视到,路由器进行规则匹配

基本切换效果

​ 1.导航栏、展示区

​ 2.请来路由器

​ 3.指定路由的具体规则(什么路径,对应着什么组件)

​ 4.形成一个一个的【???.vue】Class.vue Subject.vue

​ 在终端中使用 npm i vue-router

实现步骤:

​ 1.在src下创建router文件夹,再创建index.ts文件

// 创建一个路由器,并暴露出去

// 引入createRouter

import { createRouter, createWebHistory } from “vue-router”;

// 引入组件

import Home from “@/components/Home.vue”;

import About from “@/components/About.vue”;

import News from “@/components/News.vue”;

// 创建路由器

const router = createRouter({

history: createWebHistory(), // 路由器的工作模式

routes: [

// 一个一个的路由规则

{

path: “/home”,

component: Home,

},

{

path: “/about”,

component: About,

},

{

path: “/news”,

component: News,

},

],

});

export default router;

createWebHistory 是路由器的工作模式

​ 2.在components下创建对应的组件

​ 3.在App.vue中将路由的展示引入RouterView,以及跳转路由位置的RouterLink标签

​ import { RouterView, RouterLink } from “vue-router”;

​ template中

​ RouterLink标签to路由 active-class=””点击哪个哪个高亮

​ // 展示区

​ RouterView标签展示

​ 4.在主main文件中,将Router路由器配置

import {createApp} from ‘vue’

import App from ‘./App.vue’

import Router from ‘./router’

const app = createApp(App)

app.use(Router)

app.mount(‘#app’)

两个注意点

组件

​ 一般组件:亲手写标签出来的

​ 路由组件: 靠路由的规则渲染出来的

​ 例如: routes:[{path:’/demo’,component:Demo}]

​ 1.路由组件通常存放在pages或views文件夹,一般组件通常存放在components文件夹

​ 2.通过点击导航,视觉效果上“消失”了的路由组件,默认是被卸载的,需要的时候再去挂载

路由工作模式

​ 1.history模式

​ 优点:URL更加美观,不带有#,更接近传统的网站URL

​ 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404报错

​ const router = createRouter({history: createWebHistory()})

​ 2.hash模式

​ 优点:兼容性更好,一位内不需要服务器端处理路径

​ 缺点:URL带有#不太美观,且在SEO优化方面相对较差

​ const router = createRouter({history: createWebHashHistory()})

路由_to的两种写法

​ 第一种:to字符串写法

​ router-link active-class=”active” to=”/home”

​ 第二种:to的对象写法

​ router-link active-class=”active” :to=”{path: ‘/home’}”

路由的命名

​ const router = createRouter({

history: createWebHistory(), // 路由器的工作模式

routes: [

// 一个一个的路由规则

{

name:’zhuye’,

path: “/home”,

component: Home,

},

{

name: ‘guanyu’,

path: “/about”,

component: About,

},

{

name: ‘xinwen’,

path: “/news”,

component: News,

},

],

});

路由的命名使用

RouterLink :to=”{name:’xinwen’}” active-class=”active”

路由_嵌套路由

1.在路由器上创建children数组

{

name: “xinwen”,

path: “/news”,

component: News,

children: [

​ {

​ path: “deail”,

​ component: Detail,

​ },

],

},

然后将路由的子组件写上name,对应父组件的name,然后再父路由对应的组件下写

RouterLink to=”/news/deail”

点击即可跳转到子路由对应的组件上

再将下面想呈现子路由对应的组件的位置写RouterView标签

路由_传参

路由_query参数

​ 在父路由组件中跳转子路由组件传参两种传参方式:

第一种

​ <RouterLink :to=”/news/deail?id=${item.id}&title=${item.title}&content=${item.content}`”

第二种

RouterLink :to=”{

​ path: ‘/news/deail’,

​ query: {

​ id: item.id,

​ title: item.title,

​ content: item.content

​ }

}”

​ 在子路由对应的组件中用以下语法,即可拿到传参的proxy代理对象

​ import {useRoute} from ‘vue-router’

					 let route = useRoute()

​ 从响应式中解构属性,这个属性是没有响应式的

​ import { toRefs } from “vue”;

​ import { useRoute } from “vue-router”;

​ let route = useRoute();

​ let {query} = toRefs(route)

​ 在template的html结构中可以直接使用query拿到地址传过来的值

​ query.id

​ query.title

​ query.content

路由_params传参

​ 在父路由组件中跳转子路由组件传参两种传参方式:

第一种:

​ 父组件:

​ RouterLink :to=”`/news/deail/${item.id}/${item.title}/${item.content}”

​ RouterView

第二种:

​ 第二种方式需要path换成name只能传name值

​ 父组件:

​ RouterLink :to=”{

​ name: ‘deailName’,

​ params: {

​ id: item.id,

​ title: item.title,

​ // content: item.content

​ }

}”

​ RouterView

该如何使用params传参

​ 路由中配置:

​ {

name: “xinwen”,

path: “/news”,

component: News,

children: [

​ {

​ name: ‘deailName’,

​ path: “deail/:id/:title/:content?”, // content可传可不传

​ component: Detail,

​ },

],

},

​ 子组件接收

​ import { toRefs } from “vue”;

import { useRoute } from “vue-router”;

let route = useRoute();

let { params } = toRefs(route);

路由_props配置

第一种:在子路由中配置 props:true就会让地址传的params参数变成父传子的数据,将路由收到的所有params参数作为props传给路由组件

​ 路由配置:

​ {

name: “xinwen”,

path: “/news”,

component: News,

children: [

​ {

​ name: ‘deailName’,

​ path: “deail/:id/:title/:content?”,

​ component: Detail,

​ props: true

​ },

],

},

​ 子组件:

​ import { defineProps} from ‘vue’

defineProps([‘id’,’title’,’content’])

第二种:函数写法:可以自己决定将什么作为props给路由组件,只能对query传参

​ {

name: “xinwen”,

path: “/news”,

component: News,

children: [

​ {

​ name: ‘deailName’,

​ path: “deail”,

​ component: Detail,

​ props(route) {

​ return route.query;

​ }

​ },

],

},

​ 第三种:对象写法,可以自己决定将什么作为props传参给路由组件

​ {

name: “xinwen”,

path: “/news”,

component: News,

children: [

​ {

​ name: ‘deailName’,

​ path: “deail”,

​ component: Detail,

​ props: {

​ a: 100,

​ b: 200,

​ c:300

​ }

​ },

],

},

路由的_replace属性

​ 作用:控制路由跳转时操作浏览器历史记录的模式

​ 浏览器的历史记录有两种写入方式:分别为push和replace:

​ push是追加历史记录(默认值)

​ replace是替换当前记录

​ 开启replace模式

​ RouterLink replace …..> News<RouterLink

路由_编程式路由导航

​ 脱离RouterLink实现路由跳转,使用脚本跳转页面,语法和to一样

​ button @click=”showNewDeail(item)”

import {useRouter} from ‘vue-router’

function showNewDeail(item: interNews) {

router.replace({

path: ‘/news/deail’,

query: {

id: item.id,

title: item.title,

content: item.content

}

})

路由_重定向

​ 让指定的位置重新定位到另一个位置

​ {

path: “/“,

redirect: “/home”,

}