新特性
重写双向数据绑定
vue2基于Object.defineProperty()实现 vue3 基于Proxy
优势:丢掉麻烦的备份数据、省去for in 循环、可以监听数组变化、代码更简化、可以监听动态新增的属性;、可以监听删除的属性、可以监听数组的索引和 length 属性;
VDOM性能瓶颈
在Vue2中,每次更新diff,都是全量对比,Vue3则只对比带有标记的,这样大大减少了非动态内容的对比消耗
Fragments
vue3 允许我们支持多个根节点
<template>
<div>12</div>
<div>23</div>
</template>
Tree-Shaking支持
将全局 API 进行分块。如果你不使用其某些功能,它们将不会包含在你的基础包中
Compositions API
Setup 函数式编程 也叫vue Hook
Vite
优势
冷服务 默认的构建目标浏览器是能 在 script 标签上支持原生 ESM 和 原生 ESM 动态导入
HMR 速度快到惊人的 模块热更新(HMR)
安装
npm init vite@latest
安装依赖
在通过vite创建好项目后需要自行安装依赖
npm install
NVM
node版本管理工具
NRM
实现npm在不同源间的转换
Volar插件
在Vue3中,Volar(两个插件)比vetur更好,使用时需要禁用vetur
setup
语法糖,省略了vue的导入导出等。实现了函数式编程
内置指令
v- 开头都是vue 的指令
v-text
v-html
v-show
v-if
v-ekse
v-else-if
v-for
v-on(@)
.stop
——调用event.stopPropagation()
。.prevent
——调用event.preventDefault()
。.capture
——在捕获模式添加事件监听器。.self
——只有事件从元素本身发出才触发处理函数。.{keyAlias}
——只在某些按键下触发处理函数。.once
——最多触发一次处理函数。.left
——只在鼠标左键事件触发处理函数。.right
——只在鼠标右键事件触发处理函数。.middle
——只在鼠标中键事件触发处理函数。.passive
——通过{ passive: true }
附加一个 DOM 事件。
v-bind(:)
.camel
——将短横线命名的 attribute 转变为驼峰式命名。.prop
——强制绑定为 DOM property。3.2+.attr
——强制绑定为 DOM attribute。3.2+
v-model
v-slot(#)
v-pre
跳过该元素及其所有子元素的编译
<span v-pre>{{ this will not be compiled }}</span>
v-once
仅渲染元素和组件一次,并跳过之后的更新。
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
v-memo3.2+
缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过.
<div v-memo="[valueA, valueB]">
...
</div>
// 当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用
v-cloak
用于隐藏尚未完成编译的 DOM 模板
该指令只在没有构建步骤的环境下需要使用。
当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容
<div v-cloak>
{{ message }}
</div>
自定义指令
定义
setup内的指令,必须以v开头单词首字母大写来命名
<template>
<button @click="show = !show">开关{{show}} ----- {{title}}</button>
<Dialog v-move-directive="{background:'green',flag:show}"></Dialog> // 使用时首字母小写以-分隔
</template>
const vMoveDirective: Directive = {
created: () => {
console.log("初始化====>");
},
beforeMount(...args: Array<any>) {
// 在元素上做些操作
console.log("初始化一次=======>");
},
mounted(el: any, dir: DirectiveBinding<Value>) {
el.style.background = dir.value.background;
console.log("初始化========>");
},
beforeUpdate() {
console.log("更新之前");
},
updated() {
console.log("更新结束");
},
beforeUnmount(...args: Array<any>) {
console.log(args);
console.log("======>卸载之前");
},
unmounted(...args: Array<any>) {
console.log(args);
console.log("======>卸载完成");
},
};
生命周期钩子参数详解
第一个 el 当前绑定的DOM 元素
第二个 binding
instance:使用指令的组件实例。 value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2。 oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。 arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 "foo"。 modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。 dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中
第三个 当前元素的虚拟DOM 也就是Vnode
第四个 prevNode 上一个虚拟节点,仅在 beforeUpdate 和 updated 钩子中可用
简写
在 mounted
和 updated
时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现
<template>
<div>
<input v-model="value" type="text" />
<A v-move="{ background: value }"></A>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
el.style.background = binding.value.background
}
</script>
<style>
</style>
修饰符
修饰符串联
修饰符串联时,会按顺序依次生效
// 按循序执行先执行阻止冒泡事件在执行阻止默认事件
<a @click.stop.prevent="doThat"></a>
事件
.stop
阻止单击事件继续冒泡
.prevent
阻止默认事件
.capture
即内部元素触发的事件先在此处理,然后才交由内部元素进行处理
.once
事件将只会触发一次
.passive(特殊)
用在移动端的scroll事件,来提高浏览器响应速度,提升用户体验
会告诉浏览器你不想阻止事件的默认行为
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发, -->
<div @scroll.passive="onScroll">...</div>
按键
.enter
.tab
.delete
捕获“删除”和“退格”键
.esc
.space
.up
.down
.left
.right
示例
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` 按enter键执行 -->
<input @keyup.enter="submit" />
<!-- 按delete 键执行 -->
<input @keyup.delete="submit" />
系统
.ctrl
.alt
.shift
.meta
示例
<!-- Alt + Enter 时触发-->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click 按住加点击时触发-->
<div @click.ctrl="doSomething">Do something</div>
动态样式
Vue3和Vue2一样支持通过v-bind绑定动态样式(对象和数组)
ref全家桶
只有响应式变量才能被更新到视图,非响应式变量渲染时只渲染其初始值
ref
Ref实现了全响应式,通过.value修改值,在使用插值表达式时,可以不需要.value
ref是Ref类型的工厂函数
vue3数据默认不是响应式的,需要手动声明
ref:修复了vue2非变异方法无法响应式
可以将 ref
看成 reactive
的一个变形版本,这是由于 reactive
内部采用 Proxy 来实现,而 Proxy
只接受对象作为入参,这才有了 ref
来解决值类型的数据响应,如果传入 ref
的是一个对象,内部也会调用 reactive
方法进行深层响应转换
<script setup lang="ts">
import {ref,Ref} from 'vue' //
let message:Ref<String> = ref("zs") // 返回一个对象
const changeMes = ()=>{
message.value = 'zs1' // 在使用插值表达式时,可以不需要message.value
}
</script>
注意
ref对象的视图更新会调用triggerRef,更新当前组件的所有的shallowRef的视图
获取DOM
<template>
<div ref="dv">123</div> // ref字符串和ref()被赋值的变量(常量)名相同,对应的变量(常量)就被赋值为原生DOM对象
<button @click="getdv()">get</button>
<div>{{sref}}</div>
<button @click="setsref">点击我</button>
</template>
<script setup lang="ts">
import { customRef, ref, shallowRef } from "@vue/reactivity";
let dv = ref() // ref字符串和ref()被赋值的变量(常量)名相同,对应的变量(常量)就被赋值为原生DOM对象
function getdv(){
console.log(dv.value)
}
let sref = shallowRef({
age:13
})
function setsref(){
sref.value.age++
console.log( (dv.value as HTMLDivElement).textContent = "789") // ref获取的原生DOM被修改不会导致shallowRef的视图更新
}
</script>
<style>
</style>
shallowRef
只跟踪.value本身的变化,当value变化时才渲染DOM。
用法和ref一样
注意
ref对象的视图更新会调用triggerRef,更新当前组件的所有的shallowRef的视图
triggerRef
传入一个shallowRef,强制更新该shallowRef的视图
triggerRef(shallowef)
customRef
自定义ref,一般用于节流。类似于proxy
传入一个函数,返回一个对象
<script setup lang="ts">
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() { // 获取对象.value时调用该函数
track() // 搜集依赖
return value
},
set(newValue) { // 设置对象.value=时调用该函数
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 更新视图
}, delay)
}
}
})
}
</script>
进阶
<template>
<div>{{ s }}</div>
<button @click="c">+</button>
</template>
<script setup lang="ts">
import { customRef, ref, shallowRef } from "@vue/reactivity";
function getRef(v: any) {
return customRef((track, trigger) => {
return {
get() {
console.log("get");
track(); // 收集依赖
return v;
},
set(n: any) {
v = n;
v.trigger = trigger; // 获取customRef的trigger
console.log("set");
trigger();
},
};
});
}
let s = getRef({});
s.value = {
age: 12,
name: "张三",
};
let c = () => {
console.log(s);
s.value.age++;
s.value.trigger(); // 调用customRef的trigger更新视图
};
</script>
<style>
</style>
isRef
判断是否为ref类型
console.log(isRef(message))
reactive全家桶
reactive
reactive只支持引用类型,和ref一样实现了全响应式
使用时不需要.value
shallowReactive
shallowReactive只支持引用类型,和shallowRef一样实现了浅响应式,当视图被更新时shallowReactive也shallowRef一样会被更新
使用时不需要.value
isReactive
是否为Reactive对象
readonly全家桶
readonly
接收一个对象,返回一个全只读代理,原对象的改变会导致只读代理的改变
<template>
<div>{{s}}</div>
<button @click="change">+</button>
{{rs}}
</template>
<script setup lang="ts">
import { reactive, readonly, ref } from "@vue/reactivity";
let s = ref([1,2])
let rs = readonly(s)
function change(){
s.value.push(1)
}
</script>
<style>
</style>
shallowReadonly
接收一个对象,返回一个浅只读代理,原对象的改变会导致只读代理的改变。
并且对代理可修改部分的修改也会导致原对象的改变
<template>
<div>
{{ s }}
</div>
<button @click="cs">+</button>
<div>
{{ss}}
</div>
</template>
<script setup lang="ts">
import {
reactive,
readonly,
ref,
shallowReactive,
shallowReadonly,
shallowRef,
} from "@vue/reactivity";
let s = ref({
age:15,
hh:{
age:18
}
});
let ss = shallowReadonly(s)
function cs() {
s.value.age++;
ss.value.hh.w = 3
}
</script>
<style>
</style>
isReadonly
是否为Readonly对象
to全家桶
直接从响应式对象结构出来的不是响应式代理
toRef
将reactive对象源数据指定属性,代理到一个新的ref。所以对reactive对象指定属性的修改会反应到toRef,对toRef也会反应到reactive
<template>
<div>
{{ s }}
</div>
<button @click="cs">+</button>
<div>
{{ w }}
</div>
</template>
<script setup lang="ts">
import {
isRef,
reactive,
toRef,
} from "@vue/reactivity";
let ob = {
age: 15,
hh: {
age: 18,
},
};
let s = reactive(ob);
let w = toRef(s,"hh") // toRef
function cs() {
w.value.age++
console.log(isRef)
}
</script>
<style>
</style>
toRefs
对reactive对象所以属性调用toRef
<template>
<div>
{{ s }}
</div>
<button @click="cs">+</button>
<div>
{{ age }}
</div>
{{hh}}
</template>
<script setup lang="ts">
import {
isRef,
reactive,
toRef,
toRefs,
} from "@vue/reactivity";
let ob = {
age: 15,
hh: {
age: 18,
},
};
let s = reactive(ob);
let {age,hh} = toRefs(s) // toRefs
function cs() {
age.value++
hh.value.age++
console.log(age)
}
</script>
<style>
</style>
toRaw
toRaw()
可以返回由 reactive()
、readonly()
、shallowReactive()
或者 shallowReadonly()
创建的代理对应的原始对象
对原始对象的修改不会导致视图的变化,但是代理对象的值从原始对象获取
<template>
<div>
{{ s }}
</div>
<button @click="cs">+</button>
<button @click="t">-</button>
<div>
{{ age }}
</div>
{{w}}
</template>
<script setup lang="ts">
import {
isRef,
reactive,
ref,
toRaw,
toRef,
toRefs,
} from "@vue/reactivity";
let ob = {
age: 15,
hh: {
age: 18,
},
};
let s = reactive(ob);
let w = toRaw(s)
function cs() {
w.age++
console.log(w)
// console.log("-------------------")
// console.log(s.value.age)
}
function t(){
s.age++
}
</script>
<style>
</style>
define全家桶
defineProps
接收组件传入的props
父组件
<template>
<div class="layout">
<Menu v-bind:data="data" title="我是标题"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
</script>
子组件
<template>
<div class="menu">
菜单区域 {{ title }} // 直接使用类型title属性
<div>{{ data }}</div>
</div>
</template>
<script setup lang="ts">
// defineProps是无须引入的直接使用即可,
defineProps<{ // TypeScript可以使用传递字面量类型的纯类型语法做为参数
title:string,
data:number[]
}>()
</script>
默认值
withDefaults是个函数也是无须引入开箱即用接受一个props函数第二个参数是一个对象设置默认值
type Props = {
title?: string,
data?: number[]
}
withDefaults(defineProps<Props>(), {
title: "张三",
data: () => [1, 2, 3] // 默认值是引用类型,通过函数返回
})
defineEmits
defineEmits派发一个自定义事件
<template>
<div class="menu">
<button @click="clickTap">派发给父组件</button>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([4, 5, 6])
const emit = defineEmits(['on-click']) // 定义自定义事件
const clickTap = () => {
emit('on-click', list) // 触发自定义事件
}
</script>
defineExpose
在vue3中通过ref获取子组件的实例,只能获取子组件指定的属性
const list = reactive<number[]>([4, 5, 6])
defineExpose({
list // 向外暴露list
})
defineAsyncComponent
默认情况下,所有组件会被打包到一个js文件,如果单个文件过大会导致加载缓慢。通过defineAsyncComponent可以把指定的组件打包成单独的js文件
<script setup lang="ts">
import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
const Dialog = defineAsyncComponent(() => import('../../components/Dialog/index.vue'))
加载中
配合Suspense组件,可以实现加载中的效果
<Suspense>
组件有两个插槽。它们都只接收一个直接子节点。default
插槽里的节点会尽可能展示出来。如果不能,则展示 fallback
插槽里的节点。
<Suspense>
<template #default>
<Dialog>
<template #default>
<div>我在哪儿</div>
</template>
</Dialog>
</template>
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
computed
函数式
返回一个ref对象,只读,函数会被初始化执行一次
更新: 只有ref、reactive对象在依赖中对应的属性(值)的改变,才会导致函数重新执行
初始化:在第一次调用.value时执行(渲染也是调用了.value)
<template>
<div>
{{ s }}
</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { reactive, ref } from "@vue/reactivity";
import { computed } from "@vue/runtime-core";
let c1 = ref(1);
let c2 = reactive({
age: 18,
name: {
age: 10,
},
});
let w = 1;
let s = computed(() => { // computed接收一个函数,返回一个对象
console.log(c2.name.age);
return c1.value + "------------"+c2.age;
});
function cs() {
c2.name.age++ // 导致console.log(c2.name.age);中c2.name.age的改变,函数重新执行
}
</script>
<style>
</style>
对象式
get
get的初始化和更新和函数式一样
<template>
<div>{{ mul }}</div> // get初始化,在第一次调用.value时执行
<div @click="mul = 100">click</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
let price = ref({
age: 19,
});
let s = ref(1)
function cs(){
s.value++
}
let mul = computed({
get: () => {
console.log(s.value);
return price.value;
},
set: (value) => {
console.log(value)
price.value.age = + value;
},
});
// mul.value get初始化,在第一次调用.value时执行
</script>
<style>
</style>
set
给代理变量重新赋值会调用set,value为值
<template>
<div>{{ mul }}</div>
<div @click="mul = 100">click</div> // 给对象直接赋值会调用set函数
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
let price = ref({
age: 19,
});
let mul = computed({
get: () => {
console.log("first");
return price.value;
},
set: (value) => {
console.log(value)
price.value.age = + value;
},
});
</script>
<style>
</style>
watch
ref
基本类型
<template>
<div>{{ s }}----------{{ w }}</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "@vue/runtime-core";
let w = 1;
let s = ref(1);
function cs() {
s.value++;
}
watch(
s, // 监听多个,可以传入一个数组 n、o 也将便成数组,与之对应
(n, o) => {
console.log("n:",n); // 因为监听的是基本类型代理,所以新值和旧值是不一样的
console.log("o:",o); // 因为监听的是基本类型代理,所以新值和旧值是不一样的
},
{
deep: true,
immediate:true // 初始化执行一次
}
);
</script>
<style>
</style>
引用类型
<template>
<div>{{ s }}----------{{ w }}</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "@vue/runtime-core";
let w = 1;
let s = ref({
name: 12,
hh: {
age: 10,
},
});
function cs() {
s.value.hh.age++;
}
watch(
s, // 监听多个,可以传入一个数组 n、o 也将便成数组,与之对应
(n, o) => {
console.log("n:",n); // 因为监听的是引用类型代理,所以新值和旧值都是变化后的对象
console.log("o:",o); // 因为监听的是引用类型代理,所以新值和旧值都是变化后的对象
},
{
deep: true, // ref引用类型 默认不进行深度监听,需要开启深度监听
}
);
</script>
<style>
</style>
属性监听
<template>
<div>{{ s }}----------{{ w }}</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "@vue/runtime-core";
let w = 1;
let s = ref({
name: 12,
hh: {
age: 10,
},
});
function cs() {
s.value.hh.age++;
}
watch(
()=> s.value.hh.age, // 传入一个函数,在函数中返回一个属性 属性监听 直接s.value.hh.age是不允许的
(n, o) => {
console.log("n:",n); // 因为监听的是引用类型代理,所以新值和旧值都是变化后的对象
console.log("o:",o); // 因为监听的是引用类型代理,所以新值和旧值都是变化后的对象
},
{
deep: true, // ref引用类型 默认不进行深度监听,需要开启深度监听
}
);
</script>
<style>
</style>
reactive
reactive写不写deep都深度监听,其它效果和ref的引用类型一样
配置项
watch参数3为配置项
immediate
在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined
。
deep
如果源是对象或数组,则强制深度遍历源,以便在深度变更时触发回调。详见深层侦听器。
flush
调整回调的刷新时机
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post'
选项:
onTrack / onTrigger
调试侦听器的依赖关系。详见侦听器调试。
watchEffect
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
只监听依赖中准确出现的部分,会被初始化执行一次
<template>
<div>{{ s }}----------{{ w }}</div>
<button @click="cs">+</button>
</template>
<script setup lang="ts">
import { reactive, ref, watch, watchEffect } from "@vue/runtime-core";
let w = 1;
let s = ref({
name: 12,
hh: {
age: 10,
},
});
function cs() {
s.value.hh.age++; // 依赖中准确出现
}
watchEffect(()=>{
// console.log(s.value.hh) 非 准确出现
console.log(s.value.hh.age) // 依赖中准确出现
})
</script>
<style>
</style>
清除副作用
watchEffect((onCleanup)=>{
console.log(s.value.hh.age) // 依赖中准确出现
onCleanup(()=>{
console.log("onCleanup") // onCleanup会被先执行
})
})
停止监听
const stop = watchEffect(() => {})
// 当不再需要此侦听器时:
stop()
配置项
参数2为配置项,和watch的配置项一致
生命周期钩子
组合式 API:生命周期钩子 | Vue.js (vuejs.org)
less
需要安装less
npm install less
样式隔离
<style scoped>
</style>
组件
全局组件
在main文件中引入一次
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
createApp(App).component('Card',Card).mount('#app') // component链式调用
局部组件
使用时需要引入
<template>
<div class="wraps">
<layout-menu :flag="flag" @on-click="getMenu" @on-toogle="getMenuItem" :data="menuList" class="wraps-left"></layout-menu>
<div class="wraps-right">
<layout-header> </layout-header>
<layout-main class="wraps-right-main"></layout-main>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive,ref } from "vue";
import layoutHeader from "./Header.vue";
import layoutMenu from "./Menu.vue";
import layoutMain from "./Content.vue";
递归组件
和递归函数一样,嵌套渲染,必须有递归结束条件
<div style="margin-left:10px;" class="tree">
<div :key="index" v-for="(item,index) in data">
<div @click='clickItem(item)'>{{item.name}}
</div>
<TreeItem @on-click='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem>
</div>
</div>
动态组件
通过component内置特殊元素,让多个组件使用同一个挂载点,并动态切换
import A from './A.vue'
import B from './B.vue'
<component :is="A"></component>
异步组件
见defineAsyncComponent
内置组件
无需引入
组件名为复合词,使用时全部小写-分隔
Transition
为单个元素或组件提供动画过渡效果
interface TransitionProps {
/**
* 用于自动生成过渡 CSS class 名。
* 例如 `name: 'fade'` 将自动扩展为 `.fade-enter`、
* `.fade-enter-active` 等。
*/
name?: string
/**
* 是否应用 CSS 过渡 class。
* 默认:true
*/
css?: boolean
/**
* 指定要等待的过渡事件类型
* 来确定过渡结束的时间。
* 默认情况下会自动检测
* 持续时间较长的类型。
*/
type?: 'transition' | 'animation'
/**
* 显式指定过渡的持续时间。
* 默认情况下是等待过渡效果的根元素的第一个 `transitionend`
* 或`animationend`事件。
*/
duration?: number | { enter: number; leave: number }
/**
* 控制离开/进入过渡的时序。
* 默认情况下是同时的。
*/
mode?: 'in-out' | 'out-in' | 'default'
/**
* 是否对初始渲染使用过渡。
* 默认:false
*/
appear?: boolean
/**
* 用于自定义过渡 class 的 prop。
* 在模板中使用短横线命名,例如:enter-from-class="xxx"
*/
enterFromClass?: string
enterActiveClass?: string
enterToClass?: string
appearFromClass?: string
appearActiveClass?: string
appearToClass?: string
leaveFromClass?: string
leaveActiveClass?: string
leaveToClass?: string
}
<Transition name="fade" mode="out-in" appear>
<component :is="view"></component>
</Transition>
默认样式
通过给Transition组件的name指定值,该Transition会被添加指定的样式
进入
v就是Transition组件的name值
v-enter-from
进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除
v-enter-active
进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型
v-enter-to
进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from
被移除的同时),在过渡或动画完成之后移除
离开
v-leave-from
离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
v-leave-active
离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
v-leave-to
离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from
被移除的同时),在过渡或动画完成之后移除
自定义样式
可以修改Transition组件的属性值,自定义过渡的样式名
<!-- 假设你已经在页面中引入了 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</Transition>
进入
enter-from-class
enter-active-class
enter-to-class
离开
leave-from-class
leave-active-class
leave-to-class
appear
如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 appear
<Transition
appear
appear-from-class="from"
appear-active-class="active"
appear-to-class="to"
>
<div>
---
</div>
</Transition>
过渡事件
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
TransitionGroup
用于对 v-for
列表中的元素或组件的插入、移除和顺序改变添加动画效果
<TransitionGroup>
支持和 <Transition>
基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
- 默认情况下,它不会渲染一个容器元素。但你可以通过传入
tag
prop 来指定一个元素作为容器元素来渲染。 - 过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
- 列表中的每个元素都必须有一个独一无二的
key
attribute。 - CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
进入/离开动画
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
移动动画
移动类v-move
案例一
<script>
import { shuffle } from 'lodash-es'
const getInitialItems = () => [1, 2, 3, 4, 5]
let id = getInitialItems().length + 1
export default {
data() {
return {
items: getInitialItems()
}
},
methods: {
insert() {
const i = Math.round(Math.random() * this.items.length)
this.items.splice(i, 0, id++)
},
reset() {
this.items = getInitialItems()
},
shuffle() {
this.items = shuffle(this.items)
},
remove(item) {
const i = this.items.indexOf(item)
if (i > -1) {
this.items.splice(i, 1)
}
}
}
}
</script>
<template>
<button @click="insert">insert at random index</button>
<button @click="reset">reset</button>
<button @click="shuffle">shuffle</button>
<TransitionGroup tag="ul" name="fade" class="container">
<div v-for="item in items" class="item" :key="item">
{{ item }}
<button @click="remove(item)">x</button>
</div>
</TransitionGroup>
</template>
<style>
.container {
position: relative;
padding: 0;
}
.item {
width: 100%;
height: 30px;
background-color: #f3f3f3;
border: 1px solid #666;
box-sizing: border-box;
}
/* 1. 声明过渡效果 */
.fade-move, // 移动类v-move
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}
/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: scaleY(0.01) translate(30px, 0);
}
/* 3. 确保离开的项目被移除出了布局流
以便正确地计算移动时的动画效果。 */
.fade-leave-active {
position: absolute;
}
</style>
案例二
<template>
<div>
<button @click="shuffle">Shuffle</button>
<transition-group class="wraps" name="mmm" tag="ul">
<li class="cell" v-for="item in items" :key="item.id">{{ item.number }}</li>
</transition-group>
</div>
</template>
<script setup lang='ts'>
import _ from 'lodash'
import { ref } from 'vue'
let items = ref(Array.apply(null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
number: (index % 9) + 1
}
}))
const shuffle = () => {
items.value = _.shuffle(items.value)
}
</script>
<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 10 + 9px);
.cell {
width: 25px;
height: 25px;
border: 1px solid #ccc;
list-style-type: none;
display: flex;
justify-content: center;
align-items: center;
}
}
.mmm-move { // v-move:移动中的元素,会被添加该类
transition: transform 0.8s ease;
}
</style>
渐进延迟列表动画
<script>
import gsap from 'gsap'
const list = [ { msg: 'Bruce Lee' }, { msg: 'Jackie Chan' }, { msg: 'Chuck Norris' }, { msg: 'Jet Li' }, { msg: 'Kung Fury' }]
export default {
data() {
return {
query: ''
}
},
computed: {
computedList() {
return list.filter((item) => item.msg.toLowerCase().includes(this.query))
}
},
methods: {
onBeforeEnter(el) {
el.style.opacity = 0
el.style.height = 0
},
onEnter(el, done) {
gsap.to(el, {
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
})
},
onLeave(el, done) {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.15,
onComplete: done
})
}
}
}
</script>
<template>
<input v-model="query" />
<TransitionGroup
tag="ul"
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
>
<li
v-for="(item, index) in computedList"
:key="item.msg"
:data-index="index"
>
{{ item.msg }}
</li>
</TransitionGroup>
</template>
状态过渡动画
该动画主要通过gsap库对js变化过渡实现
<template>
<div>
<input step="20" v-model="num.current" type="number" />
<div>{{ num.tweenedNumber.toFixed(0) }}</div>
</div>
</template>
<script setup lang='ts'>
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
tweenedNumber: 0,
current:0
})
watch(()=>num.current, (newVal) => {
// num过渡对象
gsap.to(num, {
duration: 1, // js值过渡时长
tweenedNumber: newVal
})
})
</script>
<style>
</style>
KeepAlive
<KeepAlive>
包裹动态组件时,会缓存不活跃的组件实例,而不是销毁它们。
任何时候都只能有一个活跃组件实例作为 <KeepAlive>
的直接子节点。
当一个组件在 <KeepAlive>
中被切换时,它的 activated
和 deactivated
生命周期钩子将被调用,用来替代 mounted
和 unmounted
。这适用于 <KeepAlive>
的直接子节点及其所有子孙节点。
include/exclude
include:缓存指定组件
exclude:不缓存指定组件
<!-- 用逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view"></component>
</KeepAlive>
<!-- 正则表达式 (使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view"></component>
</KeepAlive>
<!-- 数组 (使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view"></component>
</KeepAlive>
max
如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间
<KeepAlive :max="10">
<component :is="view"></component>
</KeepAlive>
Teleport
将其插槽内容渲染到 DOM 中的另一个位置, 是一种能够将我们的模板渲染至指定DOM
节点,不受父级style
、v-show
等属性影响
指定目标容器
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />
有条件地禁用
<teleport to="#popup" :disabled="displayVideoInline">
<video src="./my-movie.mp4">
</teleport>
Suspense
<Suspense>
组件有两个插槽:#default
和 #fallback
。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
<Suspense>
组件会触发三个事件:pending
、resolve
和 fallback
。pending
事件是在进入挂起状态时触发。resolve
事件是在 default
插槽完成获取新内容时触发。fallback
事件则是在 fallback
插槽的内容显示时触发。
使用见defineAsyncComponent,
插槽
匿名插槽
子组件
<template>
<div>
<slot></slot> // 匿名槽 默认值为default
</div>
</template>
父组件
<Dialog>
<template v-slot> // 匿名插 默认替换default槽
<div>2132</div>
</template>
</Dialog>
具名插槽
子组件
<div>
<slot name="header"></slot> // 具名槽,header
<slot></slot> // 匿名槽
<slot name="footer"></slot> // 具名槽,footer
</div>
父组件
<Dialog>
<template v-slot:header> // v-slot: 简写 #
<div>1</div>
</template>
<template v-slot>
<div>2</div>
</template>
<template v-slot:footer>
<div>3</div>
</template>
</Dialog>
//----------------简写----------------
<Dialog>
<template #header> // v-slot: 简写 #
<div>1</div>
</template>
<template #default>
<div>2</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
作用域插槽
子组件将数据绑定到槽上,父组件通过插获取值
子组件
<div>
<slot name="header"></slot>
<div>
<div v-for="item in 100">
<slot :data="item"></slot> // 把item绑定到匿名槽的data属性
</div>
</div>
<slot name="footer"></slot>
</div>
父组件
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default="{ data }"> // 解构获取,匿名槽data属性的数据
<div>{{ data }}</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
动态插槽
插可以是一个变量名
<Dialog>
<template #[name]>
<div>23</div>
</template>
</Dialog>
const name = ref('header')
依赖注入
provide
和 inject
可以帮助我们解决Prop 逐级透传问题问题。 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖
provide
<template>
<div class="App">
<button>我是App</button>
<A></A>
</div>
</template>
<script setup lang='ts'>
import { provide, ref } from 'vue'
import A from './components/A.vue'
let flag = ref<number>(1)
provide('flag', flag) // provide 以flag为key 传入一个值
</script>
<style>
.App {
background: blue;
color: #fff;
}
</style>
inject
<template>
<div style="background-color: green;">
我是B
<button @click="change">change falg</button>
<div>{{ flag }}</div>
</div>
</template>
<script setup lang='ts'>
import { inject, Ref, ref } from 'vue'
const flag = inject<Ref<number>>('flag', ref(1)) // 获取以flag为键的值 并设置默认值
const change = () => {
flag.value = 2
}
</script>
<style>
</style>
css功能
作用域 CSS
当 <style>
标签带有 scoped
attribute 的时候,它的 CSS 只会影响当前组件的元素
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
转换为:
vue
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
深度选择器
处于 scoped
样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,可以使用 :deep()
这个伪类:
vue
<style scoped>
.a :deep(.b) {
/* ... */
}
</style>
上面的代码会被编译成:
css
.a[data-v-f3f3eg9] .b {
/* ... */
}
插槽选择器
默认情况下,作用域样式不会影响到 <slot/>
渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。使用 :slotted
伪类以明确地将插槽内容作为选择器的目标:
<style scoped>
:slotted(div) { // 子组件中声明
color: red;
}
</style>
全局选择器
如果想让其中一个样式规则应用到全局,比起另外创建一个 <style>
,可以使用 :global
伪类来实现 (看下面的代码):
vue
<style scoped>
:global(.red) {
color: red;
}
</style>
混合使用局部与全局样式
你也可以在同一个组件中同时包含作用域样式和非作用域样式:
vue
<style>
/* 全局样式 */
</style>
<style scoped>
/* 局部样式 */
</style>
CSS Modules
一个 <style module>
标签会被编译为 CSS Modules 并且将生成的 CSS class 作为 $style
对象暴露给组件:
vue
<template>
<p :class="$style.red">This should be red</p>
</template>
<style module>
.red {
color: red;
}
</style>
自定义注入名称
你可以通过给 module
attribute 一个值来自定义注入 class 对象的属性名:
<template>
<p :class="classes.red">red</p>
</template>
<style module="classes">
.red {
color: red;
}
</style>
CSS 中的 v-bind()
单文件组件的 <style>
标签支持使用 v-bind
CSS 函数将 CSS 的值链接到动态的组件状态:
vue
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style>
.text {
color: v-bind(color);
}
</style>
这个语法同样也适用于 :
vue
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color'); // JavaScript 表达式 (需要用引号包裹起来)
}
</style>
环境切换
Vite 在一个特殊的 import.meta.env
对象上暴露环境变量,在不同的环境(生产‘、开发)切换时会读取不同文件里面的内容,挂载到import.meta.env[key] 上
新建文件
在根目录下创建
开发
.env.development 并输入内容如 VITE_HTTP = www.baidu.com
开发环境需要指定文件名
package.json "dev":"vite --mode development"
生产
.env.production 并输入内容如
VITE_HTTP = www.baidu.com
生产环境不需要指定文件名,自动到根目录下读取 .env.production
pinia
安装
npm install pinia
注册
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
const store = createPinia()
let app = createApp(App)
app.use(store)
app.mount('#app')
初始化
src/store/index.ts
import { defineStore } from 'pinia'
import { Names } from './store-namespce'
export const useTestStore = defineStore(Names.Test, { // 这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools
state:()=>{
return {
current:1
}
},
//类似于computed 可以帮我们去修饰我们的值
getters:{
},
//可以操作异步 和 同步提交state
actions:{
}
})
state
直接修改
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.current++ // 直接修改
}
</script>
<style>
</style>
批量修改
实例上有$patch方法可以批量修改多个值
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$patch({ // $patch对象批量修改
current:200,
age:300
})
}
</script>
<style>
</style>
函数批量修改
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$patch((state)=>{ // $patch函数批量修改
state.current++;
state.age = 40
})
}
</script>
<style>
</style>
原始对象修改
设置$state属性,为新对象来替换store的整个状态
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$state = { // $state,替换store的整个状态
current:10,
age:30
}
}
</script>
<style>
</style>
actions修改
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
export const useTestStore = defineStore(Names.TEST, {
state:()=>{
return {
current:1,
age:30
}
},
actions:{
setCurrent () { // 定义修改方法
this.current++
}
}
})
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.setCurrent()
}
</script>
<style>
</style>
解构store
直接解构是会失去响应性的
import { storeToRefs } from 'pinia'
const Test = useTestStore()
const { current, name } = storeToRefs(Test) // 通过storeToRefs返回代理的原始对象,和store为同一对象(对store实例和解构后对象的修改,会导致另一修改)
getters
类似于computed 可以帮我们去修饰我们的值
箭头函数
使用箭头函数不能使用this this指向已经改变指向undefined 修改值请用state
getters:{
newPrice:(state)=> `$${state.user.price}`
},
普通函数
普通函数形式可以使用this
getters:{
newCurrent ():number {
return ++this.current
}
},
相互调用
getters:{
newCurrent ():number | string {
return ++this.current + this.newName
},
newName ():string {
return `$-${this.name}`
}
},
actions
可以操作异步 和 同步提交state
同步
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
export const useTestStore = defineStore(Names.TEST, {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.counter}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.randomizeCounter()
}
</script>
<style>
</style>
异步
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
type Result = {
name: string
isChu: boolean
}
const Login = (): Promise<Result> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: '小满',
isChu: true
})
}, 3000)
})
}
export const useTestStore = defineStore(Names.TEST, {
state: () => ({
user: <Result>{},
name: "123"
}),
actions: {
async getLoginInfo() {
const result = await Login()
this.user = result;
}
},
})
<template>
<div>
<button @click="Add">test</button>
<div>
{{Test.user}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.getLoginInfo()
}
</script>
<style>
</style>
常用API
在store上调用
$reset
重置store
到他的初始状态
$subscribe
类似于Vuex 的abscribe 只要有state 的变化就会走这个函数
Test.$subscribe((args,state)=>{
console.log(args,state);
})
如果你的组件卸载之后还想继续调用请设置第二个参数
Test.$subscribe((args,state)=>{
console.log(args,state);
},{
detached:true
})
$onAction
只要有actions被调用就会走这个函数
Test.$onAction((args)=>{
console.log(args);
args.after(()=>{ // after最后执行
console.log("after")
})
},true) // 组件销毁继续监听
持久化
main.ts
const store = createPinia()
store.use((context:PiniaPluginContext)=>{ // pinia创建实例后会执行该回调函数,回调函数的返回值会作为state。
// 可以从context获取pinia实例,再通过$subscribe监听state变化,执行回调函数保存
})
Vue-Router
安装
npm install vue-router@4
创建
src/router/index.ts
//引入路由对象
import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
//vue2 mode history vue3 createWebHistory
//vue2 mode hash vue3 createWebHashHistory
//vue2 mode abstact vue3 createMemoryHistory
//路由数组的类型 RouteRecordRaw
// 定义一些路由
// 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [{
path: '/',
component: () => import('../components/a.vue')
},{
path: '/register',
component: () => import('../components/b.vue')
}]
const router = createRouter({
history: createWebHistory(), // 指定路由模式
routes
})
//导出router
export default router
挂载
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
路由模式
WebHistory
创建一个 HTML5 历史,即单页面应用程序中最常见的历史记录。应用程序必须通过 http 协议被提供服务
WebHashHistory
创建一个 hash 历史记录。对于没有主机的 web 应用程序 (例如 file://
),或当配置服务器不能处理任意 URL 时这非常有用。注意:如果 SEO 对你很重要,你应该使用 createWebHistory
。
MemoryHistory
创建一个基于内存的历史记录。这个历史记录的主要目的是处理 SSR
组件
router-link
用于路由跳转,router-link不会导致页面刷新,a标签会导致页面刷新
to
-
详细内容:
表示目标路由的链接。当被点击后,内部会立刻把
to
的值传到router.push()
,所以这个值可以是一个string
或者是描述目标位置的对象<!-- 字符串 --> <router-link to="/home">Home</router-link> <!-- 渲染结果 --> <a href="/home">Home</a> <!-- 使用 v-bind 的 JS 表达式 --> <router-link :to="'/home'">Home</router-link> <!-- 同上 --> <router-link :to="{ path: '/home' }">Home</router-link> <!-- 命名的路由 --> <router-link :to="{ name: 'user', params: { userId: '123' }}">User</router-link> <!-- 带查询参数,下面的结果为 `/register?plan=private` --> <router-link :to="{ path: '/register', query: { plan: 'private' }}"> Register </router-link>
replace
-
类型:
boolean
-
默认值:
false
-
详细内容:
设置
replace
属性的话,当点击时,会调用router.replace()
,而不是router.push()
,所以导航后不会留下历史记录。<router-link to="/abc" replace></router-link>
active-class
-
类型:
string
-
默认值:
"router-link-active"
(或者全局linkActiveClass
) -
详细内容:
链接激活时,应用于渲染的
<a>
的 class。
aria-current-value
ARIA (Accessible Rich Internet Applications) 是一组属性,用于定义使残障人士更容易访问 Web 内容和 Web 应用程序(尤其是使用 JavaScript 开发的应用程序)的方法
-
类型:
'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'
(string
) -
默认值:
"page"
-
详细内容:
当链接激活时,传递给属性
aria-current
的值
custom
-
类型:
boolean
-
默认值:
false
-
详细内容:
<router-link>
是否应该将其内容包裹在<a>
元素中。在使用v-slot
创建自定义 RouterLink 时很有用。默认情况下,<router-link>
会将其内容包裹在<a>
元素中,即使使用v-slot
也是如此。传递自定义的
prop,可以去除这种行为。<router-link to="/home" custom v-slot="{ navigate, href, route }"> <a :href="href" @click="navigate">{{ route.fullPath }}</a> </router-link>
渲染成
<a href="/home">/home</a>
。<router-link to="/home" v-slot="{ route }"> <span>{{ route.fullPath }}</span> </router-link>
渲染成
<a href="/home"><span>/home</span></a>
。
exact-active-class
-
类型:
string
-
默认值:
"router-link-exact-active"
(或者全局linkExactActiveClass
) -
详细内容:
链接精准激活时,应用于渲染的
<a>
的 class。
v-slot
<router-link>
通过一个作用域插槽暴露底层的定制能力。
注意
记得把 custom
配置传递给 <router-link>
,以防止它将内容包裹在 <a>
元素内。
<router-link
to="/about"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<NavLink :active="isActive" :href="href" @click="navigate">
{{ route.fullPath }}
</NavLink>
</router-link>
href
:解析后的 URL。将会作为一个<a>
元素的href
属性。如果什么都没提供,则它会包含base
。route
:解析后的规范化的地址。navigate
:触发导航的函数。 会在必要时自动阻止事件,和router-link
一样。例如:ctrl
或者cmd
+ 点击仍然会被navigate
忽略。isActive
:如果需要应用 active class,则为true
。允许应用一个任意的 class。isExactActive
:如果需要应用 exact active class,则为true
。允许应用一个任意的 class
router-view
name
-
类型:
string
-
默认值:
"default"
-
详细内容:
如果
<router-view>
设置了name
,则会渲染对应的路由配置中components
下的相应组件。 -
更多的内容请看:命名视图
route
-
详细内容:
只有在这个路由被访问到的时候,才加载对应的组件,否则不加载
v-slot
<router-view>
暴露了一个 v-slot
API,主要使用 <transition>
和 <keep-alive>
组件来包裹你的路由组件
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<keep-alive>
<suspense>
<template #default>
<component
:is="Component"
:key="route.meta.usePathKey ? route.path : undefined"
/>
</template>
<template #fallback> Loading... </template>
</suspense>
</keep-alive>
</transition>
</router-view>
Component
: VNodes, 传递给<component>
的is
prop。route
: 解析出的标准化路由地址。
编程式导航
字符串
import { useRouter } from 'vue-router'
const router = useRouter()
const toPage = () => {
router.push('/reg')
}
对象
import { useRouter } from 'vue-router'
const router = useRouter()
const toPage = () => {
router.push({
path: '/reg'
})
}
命名
import { useRouter } from 'vue-router'
const router = useRouter()
const toPage = () => {
router.push({
name: 'Reg'
})
}
替换跳转
导航方式
<router-link to="/abc" replace></router-link>
编程方式
import { useRouter } from 'vue-router'
const router = useRouter()
const toPage = () => {
router.replace({
name: 'Reg'
})
}
路由跳转
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步
<button @click="next">前进</button>
<button @click="prev">后退</button>
const next = () => {
//前进 数量不限于1
router.go(1)
}
const prev = () => {
//后退
router.back()
}
router
用于控制路由,传递参数
route
用于从路由接收参数
路由传参
query
发送
const toDetail = (item: Item) => {
router.push({
path: '/reg',
query: item
})
}
接收
<div>品牌:{{ route.query?.name }}</div>
<div>价格:{{ route.query?.price }}</div>
<div>ID:{{ route.query?.id }}</div>
import { useRoute } from 'vue-router';
const route = useRoute()
params
注意
编程式导航 使用router push 或者 replace 的时候 改为对象形式并且只能使用name,path无效,然后传入params
发送
const toDetail = (item: Item) => {
router.push({
name: 'Reg',
params: item
})
}
接收
<div>品牌:{{ route.params?.name }}</div>
<div>价格:{{ route.params?.price }}</div>
<div>ID:{{ route.params?.id }}</div>
import { useRoute } from 'vue-router';
const route = useRoute()
动态路径
注意
动态路径,需要router的配置文件支持
const routes:Array<RouteRecordRaw> = [
{
path:"/",
name:"Login",
component:()=> import('../components/login.vue')
},
{
//动态路由参数
path:"/reg/:id",
name:"Reg",
component:()=> import('../components/reg.vue')
}
]
发送
const toDetail = (item: Item) => {
router.push({
name: 'Reg',
params: {
id: item.id
}
})
}
接收
import { useRoute } from 'vue-router';
import { data } from './list.json'
const route = useRoute()
const item = data.find(v => v.id === Number(route.params.id))
区别
query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
query 在路由配置不需要设置参数,而 params 必须设置
query 传递的参数会显示在地址栏中
params传参刷新会无效,但是 query 会保存传递过来的值,刷新不变 ;
嵌套路由
const routes: Array<RouteRecordRaw> = [
{
path: "/user",
component: () => import('../components/footer.vue'),
children: [
{
path: "",
name: "Login",
component: () => import('../components/login.vue')
},
{
path: "reg",
name: "Reg",
component: () => import('../components/reg.vue')
}
]
},
]
<div>
<router-view></router-view>
<div>
<router-link to="/">login</router-link>
<router-link style="margin-left:10px;" to="/user/reg">reg</router-link> // 注意要加上父路由路径
</div>
</div>
命名视图
命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是 default
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
{
path: "/",
components: {
default: () => import('../components/layout/menu.vue'),
header: () => import('../components/layout/header.vue'),
content: () => import('../components/layout/content.vue'),
}
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
<div>
<router-view></router-view>
<router-view name="header"></router-view>
<router-view name="content"></router-view>
</div>
重定向
字符串
const routes: Array<RouteRecordRaw> = [
{
path:'/',
component:()=> import('../components/root.vue'),
redirect:'/user1', // 字符串重定向
children:[
{
path:'/user1',
components:{
default:()=> import('../components/A.vue')
}
},
{
path:'/user2',
components:{
bbb:()=> import('../components/B.vue'),
ccc:()=> import('../components/C.vue')
}
}
]
}
]
对象
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: () => import('../components/root.vue'),
redirect: { path: '/user1' }, // 对象重定向
children: [
{
path: '/user1',
components: {
default: () => import('../components/A.vue')
}
},
{
path: '/user2',
components: {
bbb: () => import('../components/B.vue'),
ccc: () => import('../components/C.vue')
}
}
]
}
]
函数(可以传参)
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: () => import('../components/root.vue'),
redirect: (to) => { // 函数重定向
return {
path: '/user1',
query: to.query
}
},
children: [
{
path: '/user1',
components: {
default: () => import('../components/A.vue')
}
},
{
path: '/user2',
components: {
bbb: () => import('../components/B.vue'),
ccc: () => import('../components/C.vue')
}
}
]
}
]
别名
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: () => import('../components/root.vue'),
alias:["/root","/root2","/root3"], // 别名
children: [
{
path: 'user1',
components: {
default: () => import('../components/A.vue')
}
},
{
path: 'user2',
components: {
bbb: () => import('../components/B.vue'),
ccc: () => import('../components/C.vue')
}
}
]
}
]
导航守卫
前置
router.beforeEach((to, form, next) => {
console.log(to, form);
next() // 执行next函数才会被方向
})
to: Route, 即将要进入的目标 路由对象; from: Route,当前导航正要离开的路由; next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。 next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
后置
router.afterEach((to,from)=>{
Vnode.component?.exposed?.endLoading()
})
路由元信息
通过路由记录的 meta
属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据,例如:
- 权限校验标识。
- 路由组件的过渡名称。
- 路由组件持久化缓存 (keep-alive) 的相关配置。
- 标题名称
我们可以在导航守卫或者是路由对象中访问路由的元信息数据。
declare module 'vue-router' { // TS约束,通过扩展 RouteMeta 接口来输入 meta 字段
interface RouteMeta { //
title?: string
}
}
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/views/Login.vue'),
meta: {
title: "登录"
}
},
{
path: '/index',
component: () => import('@/views/Index.vue'),
meta: {
title: "首页",
}
}
]
})
过渡动画
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot API:
declare module 'vue-router'{
interface RouteMeta {
title:string,
transition:string,
}
}
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/views/Login.vue'),
meta:{
title:"登录页面",
transition:"animate__fadeInUp",
}
},
{
path: '/index',
component: () => import('@/views/Index.vue'),
meta:{
title:"首页!!!",
transition:"animate__bounceIn",
}
}
]
})
<router-view #default="{route,Component}">
<transition :enter-active-class="`animate__animated ${route.meta.transition}`">
<component :is="Component"></component>
</transition>
</router-view>
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior
方法
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from, savedPosition) { // savedPosition有left和top两个属性,在路由跳转后更新。第一次跳转到某个路由时savedPosition为null
// return 期望滚动到哪个的位置
}
})
该函数可以返回一个 ScrollToOptions
位置对象:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终滚动到顶部
return { top: 0 }
},
})
你也可以通过 el
传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,top
和 left
将被视为该元素的相对偏移量。
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终在元素 #main 上方滚动 10px
return {
// 也可以这么写
// el: document.getElementById('main'),
el: '#main',
top: -10,
}
},
})
如果返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动。
返回 savedPosition
,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
})
如果你要模拟 “滚动到锚点” 的行为:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
}
}
},
})
如果你的浏览器支持滚动行为,你可以让它变得更流畅:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
}
}
}
})
延迟滚动
有时候,我们需要在页面中滚动之前稍作等待。例如,当处理过渡时,我们希望等待过渡结束后再滚动。要做到这一点,你可以返回一个 Promise,它可以返回所需的位置描述符。下面是一个例子,我们在滚动前等待 500ms
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
},
})
路由懒加载
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: () => import('./views/UserDetails')}], // Vue Router **只会在第一次进入页面时才会获取这个函数**,然后使用缓存数据。
})
动态路由
我们一般使用动态路由都是后台会返回一个路由表前端通过调接口拿到后处理(后端处理路由)
添加路由
新增加的路由与当前位置相匹配,就需要你用 router.push()
或 router.replace()
来手动导航,才能显示该新路由
注意一个事项vite在使用动态路由的时候无法使用别名@ 必须使用相对路径
const initRouter = async () => {
const result = await axios.get('http://localhost:9999/login', { params: formInline });
result.data.route.forEach((v: any) => {
router.addRoute({
path: v.path,
name: v.name,
component: () => import(`../views/${v.component}`) //这儿不能使用@,vite打包只能使用相对路径
})
router.push('/index')
})
console.log(router.getRoutes());
}
删除路由
通过使用 router.removeRoute()
按名称删除路由
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
API
router.hasRoute()
检查路由是否存在
router.getRoutes()
获取一个包含所有路由记录的数组
性能优化
参数说明
LCP
Largest Contentful Paint,最大内容绘制时间,页面最大的元素绘制完成的时间。
TTI
Time to Interactive,从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
TBT
Total Blocking Time,记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。
CLS
Cumulative Layout Shift,计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
打包分析图
由于我们使用的是vite vite打包是基于rollup 的我们可以使用 rollup 的插件
npm install rollup-plugin-visualizer
vite.config.ts 配置 记得设置open 不然无效
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [vue(), vueJsx(),visualizer({
open:true
})],
然后进行npm run build打包
Vite 配置优化
build:{
chunkSizeWarningLimit:2000,
cssCodeSplit:true, //css 拆分
sourcemap:false, //不生成sourcemap
minify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
assetsInlineLimit:5000 //小于该值 图片将打包成Base64
},
图片懒加载
import lazyPlugin from 'vue3-lazy'
<img v-lazy="user.avatar" >
虚拟列表
当数据量庞大时,只对需要UI交互的数据做显示
Element Plus有组件可用