vue
Vue介绍
介绍Vue
Vue是一套用于构建用户界面的渐进式框架。与其他大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
MVVM(Model-View-ViewModel)架构
- View:视图层(UI用户界面)
- ViewModel:业务逻辑层(一切js可视为业务逻辑)
- Model:数据层(存储数据及对数据的处理如增删改查)
View是作为视图层,简单来说可以把它理解为HTML页面;Model 是作为模型层,它是负责处理业务逻辑以及和服务器端进行交互的;ViewModel 是作为视图模型层,也就是 Vue 框架所起到的作用了,主要是作为 View 层和 Model 层之间的通信桥梁。
为什么学习Vue
- Vue是目前前端最火的框架之一
- Vue是目前企业技术栈中要求的知识点
- Vue可以提升开发体验
- Vue学习难度较低
环境搭建
安装nvm/nodejs
window下安装并使用nvm(含卸载node、卸载nvm、全局安装npm)_window安装nvm-CSDN博客
在vsCode中安装插件
搜索volar
如果使用vue2
需要将vetur
。
Volar为vue文件提供代码高亮和语法支持。
vsCode汉化
- 打开VSCode——输入快捷键“Ctrl+shift+p”,打开命令行输入框,在输入框中输入“configure display language”。
- 选择“Install additional languages...”。
- 在出现的扩展插件列表中找到“Chinese(Simplified)Language Pack for Visual Studio Code”——选择“Install”。
- 下载完成后,右下角会出现“重启”提醒——点击“Restart”重启。
配置代码片段
VSCode 如何配置用户代码片段_vscode配置代码片段-CSDN博客
设置>用户代码片段
{
"Print to console": {
"prefix": "vue3",
"body": [
"<template>",
" <div></div>",
"</template>",
"<script setup>",
"import {ref,reactive} from 'vue'",
"",
"</script>",
"<style scoped>",
"",
"</style>",
],
"description": "Log output to console"
}
}
构建前端项目
vite
npm create vite@latest vueDemo
cd vueDemo
npm install //安装依赖
npm run dev //运行
vue 脚手架
npm install @vue/cli -g
vue create <project>
通过命令配置镜像
切换为淘宝镜像命令(安装一些package容易报错)
npm config set registry https://registry.npm.taobao.org
查看当前使用的镜像地址命令
npm config get registry
如果返回 https://registry.npm.taobao.org,说明镜像配置成功。
切换回原镜像(安装一些package不容易报错)
npm config set registry https://registry.npmjs.org
cnpm 安装
npm install -g cnpm --registry=https://registry.npm.taobao.org
Vue模板语法&指令
模板插值语法
在script声明一个变量可以直接在template使用,用法为{{name}}
方法一:
<script>
export default{
setup(){
const name='小明'
return {
name
}
}
}
</script>
<template>
<div>{{name}}</div>
</template>
方法二(推荐):
<script setup>
const name='小明'
</script>
<template>
<div>{{name}}</div>
</template>
<style scoped>
</style>
在{{}}中支持运算,三元算法
指令
v-开头的都是vue的指令
v-text
用来显示文本
v-htm
l用来展示富文本
v-if
用来控制元素的显示隐藏(切换真假DOM)
v-else-if
表示v-if
的else if块
,可以链式调用
v-else
v-if
条件收尾语句
v-show
用来控制元素的显示隐藏(display none block CSS切换)
v-on
简写@用来给元素添加事件
v-bind
简写:
用来绑定元素的属性Attr
v-model
双向绑定
v-for
用来遍历元素
v-on
修饰符 冒泡案例
v-once
性能优化只渲染一次
v-pre
跳过该元素及其所有子元素的编译
v-memo
性能优化会有缓存
Ref
只有被ref包裹的变量才是响应式的。
ref()
接收参数,并将其包裹在一个带有 .value
属性的 ref 对象中返回
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Reactive
reactive
是另一种声明响应式状态的方式
与ref
的区别
ref
支持所有的类型,reactive
支持引用类型(Array
,Object
,Map
,Set
)ref
取值赋值都需要加上.value
,reactive
是不需要.value
reactive
是proxy
代理的对象,不能直接赋值,否则会破坏响应式对象的
const list = reactive([])
const list2 = reactive({ arr: [] })
function add () {
setTimeout(() => {
// 方法一:数组可以使用push加解构
let res = ["arr1", "arr2", "arr3"]
list.push(...res)
//方法二:添加一个对象,把数组作为一个属性去解决
list2.arr = res
},2000)
}
computed用法
计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。
let firstName = ref('张')
let lastName = ref('三')
//1.选项式写法:支持一个对象传入get函数以及set函数自定义操作
const name1 = computed({
get () {
return firstName.value + '-' + lastName.value
},
set (newVal) {
console.log(newVal)
// console.log(newVal.split('-'))
console.log(lastName.value)
lastName.value = newVal.split('-')[1]
firstName.value = newVal.split('-')[0]
}
})
//2.函数式写法:只能支持一个getter函数不允许修改值的
const name2 = computed(() => {
return firstName.value + '-' + lastName.value
})
function changeName () {
name1.value = '小-满'
}
watch侦听器
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
在组合式 API 中,我们可以使用 watch
函数在每次响应式状态发生变化时触发回调函数
const msg = ref('dddd')
watch(msg, (newVal, oldVal) => {
console.log(newVal, oldVal)
}, {
deep: true,//深度监听
immediate: true,//立即执行一次,默认false
flush: 'pre',//pre:组件更新之前调用,sync:同步执行,post:组件更新之后执行
})
watchEffect侦听器
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
console.log('message', message.value);
console.log('message2', message2.value);
})
认识组件
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
生命周期
简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期。
Vue3组合式API 是没有 beforeCreate 和 created 这两个生命周期的
。
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
父子组件传参
父组件通过v-bind
绑定一个数据,然后子组件通过defineProps
接受传过来的值。
代码:
给子组件传递字符串不需要v-bind
,传递变量需要,
给子组件传值
<template>
<div>父组件</div>
<hr>
<sonVue title="传入的值1"></sonVue>
<sonVue :title="data_val"></sonVue>
</template>
<script setup>
import {ref} from 'vue'
import sonVue from './son.vue';
const data_val='传入的值2'
</script>
<template>
<div>子组件</div>
<hr>
{{title}}
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
title: {
type: String,
default: '默认值'
}
})
console.log(props.title)
</script>
子组件给父组件传值:defineEmits
<template>
<div>子组件</div>
<hr>
<button @click="changeClick">给父组件传值</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['subBack'])
function changeClick () {
emit('subBack', '传回去的值')
}
</script>
<template>
<div>父组件</div>
<hr>
<sonVue @subBack="getValFun"></sonVue>
</template>
<script setup>
import { ref, reactive } from 'vue'
import sonVue from './son.vue';
const data_val = '传入的值2'
function getValFun (val) {
console.log(val)
}
</script>
给父组件传递属性或函数:defineExpose
template>
<div>子组件</div>
<hr>
<button @click="changeClick">给父组件传值</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['subBack'])
function changeClick () {
emit('subBack', '传回去的值')
}
defineExpose({
name: '小满',
open: () => console.log(1)
})
</script>
<template>
<div>父组件</div>
<hr>
<sonVue @subBack="getValFun" ref="sonFun"></sonVue>
</template>
<script setup>
import { ref, reactive } from 'vue'
import sonVue from './son.vue';
const data_val = '传入的值2'
function getValFun (val) {
console.log(val)
console.log(sonFun.value.name)
sonFun.value.open()
}
const sonFun = ref() //接收组件实例
</script>
局部组件
一个页面里由很多部分组成,就可以将其拆分为组件,在页面中引用
全局组件
一个组件使用频次较高,可以注册为全局组件。
在main.js
中注册,然后全局使用
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import CardVue1 from './components/Card1.vue'
const app = createApp(App)
app.component('CardVue1', CardVue1)
app.use(router).mount('#app')
递归组件
原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏。
<template>
<div v-for="item in data" class="tree">
<input type="checkbox" v-model="item.checked">
<span>{{item.name}}</span>
<!-- 在组件中引用自身,不需要重新使用import引入 -->
<!-- <Tree :data="item?.children" v-if="item?.children?.length"></Tree> -->
<xiaoman :data="item?.children" v-if="item?.children?.length"></xiaoman>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
data: Array
})
</script>
<script>
//如果需要更改组件名,使用下面的方法
export default {
name: 'xiaoman'
}
</script>
<style scoped>
.tree {
padding-left: 10px;
}
</style>
注释:可选链运算符(?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
运算符的功能类似于 .
链式运算符,不同之处在于,在引用为空 (nullish ) (null
或者 undefined
) 的情况下不会引起错误,该表达式短路返回值是 undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
。
动态组件
动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
在挂载点使用component
标签,然后使用v-bind:is=”组件”
插槽slot
插槽就是子组件中的提供给父组件使用的一个占位符,用
匿名插槽
子组件
<div>
<slot></slot>
</div>
父组件
<indexSlotVue>
<template v-slot>
<div>
我是插槽呀!
</div>
</template>
</indexSlotVue>
具名插槽
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
子组件
<div class="content">
<header>
<slot name="header"></slot>
</header>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父组件
<indexSlotVue>
<template v-slot:header>
<div>
顶部插槽
</div>
</template>
<template v-slot:footer>
<div>
底部插槽
</div>
</template>
</indexSlotVue>
插槽简写
<indexSlotVue>
<template #header>
<div>
顶部插槽
</div>
</template>
<template #footer>
<div>
底部插槽
</div>
</template>
</indexSlotVue>
作用域插槽
在子组件动态绑定参数 派发给父组件的slot去使用
<div v-for="item in data">
<slot :data="item"></slot>
</div>
const data = reactive([
{ name: 'slot1', value: 1 },
{ name: 'slot2', value: 2 },
{ name: 'slot3', value: 3 },
])
<indexSlotVue>
<template v-slot="{data,index}">
<div>
{{data}}-{{data.name}}-{{data.value}}-{{index}}
</div>
</template>
</indexSlotVue>
动态插槽
插槽可以是一个变量名
<indexSlotVue>
<template #[name]>
<div>
顶部插槽
</div>
</template>
</indexSlotVue>
const name=ref('header')
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块 并且减少主包的体积,这时候就可以使用异步组件。
使用defineAsyncComponent
引入
const indexSlotVue = defineAsyncComponent(() => import('../components/Slot/indexSlot.vue'))
Teleport传送组件
<Teleport>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
to
是挂载的DOM
节点,disabled
是否挂载,true
不允许挂载。
<Teleport to="#modals">
<div>A</div>
</Teleport>
keep-alive缓存组件
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。
开启keep-alive生命周期的变化
- 初次进入时:onMounted>onActivated
- 退出后触发deactivated
- 再次进入;只会触发onActivated
- 事件挂载的方法等,只执行一次的放在onMounted中;组件每次进去执行的方法放在onActived中。
兄弟组件传值&Bus
借助父组件传值
父组件
<template>
<div>
<AVue @on-click="getFlag"></AVue>
<BVue :flag="Flag"></BVue>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import AVue from './A.vue';
import BVue from './B.vue';
const Flag = ref(false)
function getFlag (flag) {
Flag.value = flag
}
</script>
子组件A
<template>
<div class="A_brother">
<button @click="changeClick">传递flag的值</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['on-click'])
let flag = false
function changeClick () {
flag = !flag
emit("on-click", flag)
}
</script>
子组件B
<template>
<div class="B_brother">
{{flag}}
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
flag: Boolean
})
</script>
Bus
安装mitt
包
npm i mitt -S
局部使用
*建议局部使用
bus.js
import mitt from 'mitt'
const bus = mitt()
export default bus
子组件A
<template>
<div class="A_brother">
<button @click="changeClick">传递flag的值</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import bus from '../../Bus';
// const emit = defineEmits(['on-click'])
let flag = false
function changeClick () {
flag = !flag
// emit("on-click", flag)
bus.emit('on-click', flag)
}
子组件B
<template>
<div class="B_brother">
{{flag}}
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import bus from '../../Bus';
// const props = defineProps({
// flag: Boolean
// })
const flag = ref(false)
onMounted(() => {
bus.on('on-click', (val) => {
flag.value = val
})
})
全局使用
在main.js中添加
import { createApp } from 'vue'
import App from './App.vue'
// 1. 引入 mitt,是一个函数
import mitt from 'mitt'
// 2. 调用 mitt
const Mit = mitt()
// 3. TypeScript注册
// 由于必须要拓展 ComponentCustomProperties 类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
// 4. vue3 挂载全局 API
app.config.globalProperties.$Bus = Mit
app.mount('#app')
scss安装
安装scss
npm install sass sass-loader --save-dev
全局使用,在vite.config.js中添加
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import '@/styles/main.scss';`
}
}
}
});
less安装
npm i less less-loader
全局使用,在vite.config.js中添加
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
less: {
additionalData: `@import '@/styles/main.less';`
}
}
}
});
Element Plus安装
npm install element-plus
完整引入
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
按需引入
按需自动引入配置完之后,在组件中可直接使用,不需要引用和注册
npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.js
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
修改主题色
AutoImport({
resolvers: [ElementPlusResolver({
importStyle: 'sass',//添加
})],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "./src/assets/scss/element.scss" as *;@use "./src/assets/scss/style.scss" as *;`
}
}
},
新建一个element.scss文件
// styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with ($colors: ('primary': ('base': #4B70E2,
),
),
);
// 如果只是按需导入,则可以忽略以下内容。
// 如果你想导入所有样式:
// @use "element-plus/theme-chalk/src/index.scss" as *;
Ant Design Vue 安装
npm i --save ant-design-vue@4.x
完整引入
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import App from './App';
import 'ant-design-vue/dist/reset.css';
const app = createApp(App);
app.use(Antd).mount('#app');
部分引入
全局部分引入
import { createApp } from 'vue';
import { Button, message } from 'ant-design-vue';
import App from './App';
const app = createApp(App);
/* 会自动注册 Button 下的子组件, 例如 Button.Group */
app.use(Button).mount('#app');
组件部分引入(不推荐)
<template>
<a-button>Add</a-button>
</template>
<script>
import { Button } from 'ant-design-vue';
const ButtonGroup = Button.Group;
export default {
components: {
AButton: Button,
AButtonGroup: ButtonGroup,
},
};
</script>
按需引入
ant-design-vue
默认支持基于 ES modules 的 tree shaking,直接引入 import { Button } from 'ant-design-vue';
就会有按需加载的效果。
自动按需引入
// vite.config.js
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [AntDesignVueResolver()],
}),
Components({
resolvers: [AntDesignVueResolver({
importStyle: false, // css in js
})],
}),
],
})
修改主题色
在 ConfigProvider 中配置主题
通过在 ConfigProvider 中传入 theme
,可以配置主题
<template>
<a-config-provider
:theme="{
token: {
colorPrimary: '#00b96b',
},
}"
>
<a-button />
</a-config-provider>
</template>
使用预设算法
通过修改算法可以快速生成风格迥异的主题,4.0 版本中默认提供三套预设算法,分别是默认算法 theme.defaultAlgorithm
、暗色算法 theme.darkAlgorithm
和紧凑算法 theme.compactAlgorithm
。你可以通过修改 ConfigProvider 中 theme
属性的 algorithm
属性来切换算法。
<template>
<a-config-provider
:theme="{
algorithm: theme.darkAlgorithm,
}"
>
<a-button />
</a-config-provider>
</template>
<script setup>
import { theme } from 'ant-design-vue';
</script>
Router
前言
Vue是单页面应用不会有那么多的HTML让我们跳转,所以需要使用路由做页面的跳转。
Vue路由允许我们通过不同的URL访问不同的内容。通过Vue可以实现多视图的单页Web应用
安装
npm install vue-router
在src下新建router文件夹,新建index.js
// import VueRouter from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/index',
name: 'index',
component: () => import('../views/index.vue')
},
{
path: '/', // 重定向
redirect: (_) => {
return { path: '/index' }
},
},
{
path: '/:currentPath(.*)*', // 路由未匹配到,进入这个
redirect: (_) => {
return { path: '/404' }
},
},
]
// Vue-router新版本中,需要使用createRouter来创建路由
export default createRouter({
// 指定路由的模式,此处使用的是web模式
history: createWebHistory(),
routes // short for `routes: routes`
})
在main.js中添加
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router).mount('#app')
路由模式
- createWebHashHistory:创建一个 hash 模式的历史。在没有主机的 web 应用 (如
file://
) 或无法通过配置服务器来处理任意 URL 的时候非常有用。路由带#
- createWebHistory:创建一个 HTML5 历史。对于单页应用来说这是最常见的历史。(经常使用的)
- createMemoryHistory:用于处理服务端渲染的。SSR。
路由使用
在App.vue
<router-view />
路由跳转
html中跳转
<router-link to="/index">跳转</router-link>
<router-link :to="{name:'index'}">跳转</router-link>
<router-link :to="{path:'/index'}">跳转</router-link>
js中跳转
import {useRouter} from 'vue-router'
const router=useRouter()
function goUrl(){
router.push({name:'index'})
router.push({path:'/index'})
}
历史记录
无历史记录操作
replace:程序式地通过替换历史栈中的当前记录来导航到一个新的 URL
<router-link replace :to="{name:'index'}">跳转</router-link>
router.replace({name:'index'})
路由传参
params
传参
-
首先需要在路由表中配置 冒号+参数(
/user/:id
)// 这是动态路由 加上:/:id
{
path: "/routers/:id",
name: "Routers",
meta: { title: "动态路由" },
component: () => import("../views/routers/routers.vue")
}, -
开始跳转并传参
goUrl() { router.push({ path: "/routers/123" }); router.push({name:'routers',params:{id:'123'}}) }
-
获取动态路由
import {useRoute} from 'vue-router' const route=useRoute() let val1=route.params //{id: '123'} let val2=route.params.id//123
注意:当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) route 对象
watch(route, (val) => {
console.log(val)
})
//或者
watch(() => route.params.id, (val) => {
console.log(val)
})
query传参
这种传参方式会把你的参数以问号拼接到路由上面
-
不需要配置路由表
-
设置query并跳转
router.push({name:'routers',query:{id:'123'}})
-
获取参数
route.query.id
嵌套路由
{
path: '/page1',
name: 'page1',
component: () => import('../views/page1.vue'),
children:[{
path:'list',
name:'list',
component:()=>import('../views/page2.vue')
}]
},
命名视图
可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是default。
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用components配置
{
path: '/page7',
name: 'page7',
components: {
default: () => import('../views/page6.vue'),
header: () => import('../views/page5.vue'),
content: () => import('../views/page4.vue'),
}
},
<RouterView />
<router-view name="header"></router-view>
<router-view name="content"></router-view>
重定向
{
path: '/:currentPath(.*)*', // 路由未匹配到,进入这个
// redirect:'index',
// redirect:{path:'/index'},
redirect: (_) => {
return { path: '/index' }
},
}
别名
{
path: '/page1',
name: 'page1',
alias:['page10'],
component: () => import('../views/page1.vue')
}
导航守卫
全局前置守卫
router.beforeEach
router.beforeEach((to, from, next) => {
console.log(to, form)
next()
})
每个守卫方法接收三个参数:
to
:Route
,即将要进入的目标,路由对象;from
:Route
,当前导航正要离开的路由;next()
:进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed
(确认的);next(false)
:中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
可以用于权限判断
const whileList = ['/']
router.beforeEach((to, from, next) => {
let token = localStorage.getItem('token')
//白名单 有值 或者登陆过存储了token信息可以跳转 否则就去登录页面
if (whileList.includes(to.path) || token) {
next()
} else {
next({
path:'/'
})
}
})
全局后置钩子
router.afterEach,不会接受 next
函数也不会改变导航本身,一般可以用来做loadingBar
router.afterEach((to, from) => {
console.log(to,from)
})
路由元信息
通过路由记录的meta属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据:
- 权限校验标识。
- 路由组件的过渡名称。
- 路由组件持久化缓存(keep-alive)的相关配置。
- 标题名称。
我们可以在导航守卫或者是路由对象中访问路由的元信息数据。
{
path: 'index',
name: 'index',
component: () => import('../views/page5.vue')
meta:{
title:'首页',//标题
requiresAuth:true //查看权限
}
}
访问
router.beforeEach((to, form, next) => {
console.log(to, form)
console.log(to.meta.title)
next()
})
路由过渡效果
Pinia
全局状态管理
特点:
- 完整的ts的支持;
- 足够轻量,压缩后的体积只有1kb左右
- 去除木塔同时,只有state,getters,actions
- action是支持同步和异步
- 代码扁平化没有模块嵌套,只有store的概念,store之间可以自由使用,每一个store都是独立的。
- 无需手动添加store,store一旦创建变会自动添加
安装
npm i pinia
引入
main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia).mount('#app')
初始化仓库Store
1.新建一个文件夹Store
2.新建文件
3.定义仓库Store
import {defineStore} from 'pinia'
4. defineStroe()需要一个唯一的名称,作为第一个参数传递
import { defineStore } from 'pinia'
export const useTestStore = defineStore("Test", {
})
5.定义值
state是箭头函数,返回一个对象,在对象里面定义值
import { defineStore } from 'pinia'
export const useTestStore = defineStore("Test", {
state:()=>{
return {
name:'bule'
}
},
//类似于computed 可以帮我们去修饰我们的值
getters:{
},
//可以操作异步 和 同步提交state
actions:{
}
})
修改值
<template>
pina:{{Test.current}}/{{Test.name}}
<hr>
<button @click="changeClick1()">Test1</button>
<button @click="changeClick2()">Test2</button>
<button @click="changeClick3()">Test3</button>
<button @click="changeClick4()">Test4</button>
<button @click="changeClick5()">Test5</button>
</template>
<script setup>
import { ref, reactive, toRaw } from 'vue'
import indexVue from '../components/Brother/index.vue';
import { useTestStroe } from '@/store'
const Test = useTestStroe()
function changeClick1 () {
//直接修改
Test.current++
}
function changeClick2 () {
//批量修改
Test.$patch({ current: 999,name:'测试' })
}
function changeClick3 () {
//批量函数修改形式,可以自定义修改逻辑,推荐使用此法
Test.$patch((state) => {
state.current++
})
}
function changeClick4 () {
//通过原始对象修改整个实例,必须修改全部的值,不推荐
Test.$state = { current: 9999, name: '123' }
}
function changeClick5 () {
//使用actions,可以传参,此法也推荐
Test.setCurrent()
}
</script>
//store中的actions
actions: {
setCurrent () {
this.current++
}
}
结构store
直接结构不具有响应式,可以使用storeToRefs解决
<template>
pinia源值:{{Test.current}}/{{Test.name}}
<hr>
pinia结构值:{{current}}/{{name}}
<hr>
<button @click="changeClick()">change</button>
</template>
<script setup>
import { ref, reactive, toRaw } from 'vue'
import indexVue from '../components/Brother/index.vue';
import { useTestStroe } from '@/store'
import { storeToRefs } from 'pinia';
const Test = useTestStroe()
//pinia 直接结构不具有响应式
// const { current, name } = Test
//使用 storeToRefs 结构具有响应式
const { current, name } = storeToRefs(Test)
function changeClick () {
Test.current++
}
</script>
actions 使用
import { defineStore } from 'pinia'
let result = {
name: '张三',
age: 30
}
function Login () {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: '张三',
age: 40
})
})
})
}
export const useTestStroe = defineStore('user', {
state: () => {
return {
user: {},
name: '小满',
current: 1
}
},
//类似computed,修饰一些值
getters: {
},
//类似methods,可以做同步,异步操作,提交state
actions: {
async setUser () {
//同步
this.user = result
//异步
const result = await Login()
this.user = result
}
}
})
getters
//类似computed,修饰一些值,有缓存的,也可以相互调用
getters: {
newName () {
return `$-${this.name}-${this.getUserAge()}`
},
getUserAge () {
return this.user.age
}
},
$reset
$reset
重置store的值
function resetClick(){
Test.$reset()
}
数据持久化
安装插件
npm i pinia-plugin-persistedstate
main.js中注册
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(router).use(pinia).mount('#app')
模块中开启持久化
export const useTestStroe = defineStore('user', {
//若仅配置true,则全部存储
//persist: true,
persist:{
//存储的key,默认是defineStore的第一个参数
key:"B",
//存储的位置,默认localStorage
storage:localStorage,
//指定的内容
paths:['name']
}
....省略
})
模块化引用
pinia可以定义多个defineStore,按需求引用,或将整合到一个文件中
import { useTestStroe } from './model/user.js'
import { useDepartStroe } from './model/depart.js'
export const appStore = {
useTestStroe: useTestStroe(),
useDepartStroe: useDepartStroe()
}
vite.config.js配置
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteCompression from 'vite-plugin-compression'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// 导入对应包
import ElementPlus from 'unplugin-element-plus/vite'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
// 环境变量
const env = loadEnv(mode, process.cwd())
// 生产环境判断
const isEnvProduction = mode === 'production'
return {
plugins: [vue({
script: {
defineModel: true,
}
}),
AutoImport({
resolvers: [ElementPlusResolver({
importStyle: 'sass',
})],
}),
Components({
resolvers: [ElementPlusResolver({
importStyle: 'sass'
})],
}),
viteCompression({
//生成压缩包gz
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
],
define: {
'process.env': {}
},
base: '/',
server: {//本地服务
host: '0.0.0.0',//ip
port: 19915,//端口号
open: true,//是否自动在浏览器打开
https: false,//是否开启https
//反向代理(跨域)
proxy: {
'/api': {
target: 'http://localhost:5000/',
// target: 'http://116.63.157.19/',
changeOrigin: true,
rewrite: (pathStr) => pathStr.replace(new RegExp('^/api'), '')
},
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "./src/assets/scss/element.scss" as *;@use "./src/assets/scss/style.scss" as *;`
}
}
},
// 强制预构建插件包
// optimizeDeps: {
// include: ['axios'],
// },
//别名
resolve: {
alias: {
"@": path.resolve(__dirname, './src'),
"@api": path.resolve(__dirname, './src/api'),
"@assets": path.resolve(__dirname, "./src/assets"),
"@views": path.resolve(__dirname, "./src/views"),
}
},
build: {
// 打包路径
assetsDir: "./static",
// 压缩
// Terser 相对较慢,但大多数情况下构建后的文件体积更小。ESbuild 最小化混淆更快但构建后的文件相对更大。
//启用/禁用 CSS 代码拆分
cssCodeSplit: true,
minify: !isEnvProduction ? 'esbuild' : 'terser',
terserOptions: {
compress: {
// 生产环境去除console
drop_console: isEnvProduction,
drop_debugger: isEnvProduction
},
output: {
// 去掉注释内容
comments: true,
},
},
// 取消计算文件大小,加快打包速度
brotliSize: false,
sourcemap: false,
outDir: 'dist',
rollupOptions: {
input: {
// 入口文件
main: path.resolve(__dirname, "index.html"),
// 其他入口
// nested: resolve(__dirname, 'xxxx.html')
},
output: {
// chunkFileNames: 'static/js/[name]-[hash].js',
// entryFileNames: 'static/js/[name]-[hash].js',
// assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks (id) { //静态资源分拆打包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
},
},
},
}
})