Vue.js
这是一篇复习VUE生态所记下的笔记,包括:Vue2、ElementUI、Vuex、Vue3、Pinia、Vite、ElementPlus,记到一半,文件搞丢了一部分,寥寥草草的补充了一下(关于随时保存的好习惯是如何养成的)。
Vue2
本文档仅记录知识要点,系统学习请移步官方文档
vue框架的两个特点:
数据驱动视图 、 双向数据绑定
MVVM
MVVM是vue实现数据驱动视图和双向数据绑定的核心原理。
MVVM:model view viewmodel
<img src="https://www.reboots.top/api/file/694b7938355d4084897239c0502b673e.png" alt="image-20240612151423706" style="zoom: 50%;" />
viemodel 是mvvm的核心,是他将view和model绑定起来,完成数据的双向绑定。
Event
在vue的@click中如果要获取原生dom事件对象,需要这么做
<button @click='fun(1,$event)'>...</button>
$event是vue内置的变量,值就是原生的dom事件对象。
如果函数没有形参列表,默认提供一个变量到第一个参数,来存储dom事件对象
事件修饰符
示例:阻止默认事件
<button @click.prevent='fun(1,$event)'>...</button>
修饰符:
| 修饰符 | 事件 |
|---|---|
| prevent | 阻止默认行为 |
| stop | 阻止冒泡 |
| capture | 捕获模式触发当前事件函数 |
| once | 只触发一次 |
| self | 阻止事件传递触发,只触发自己的事件 |
Filter
filters,定义到filters节点下,且只能用于插值语法和v-bind中
定义过滤器:
filters:{
testFilter(text){
log(text)
return 'test'
}
}
使用
{{message | testFilter}}
v-bind:id="message | testFilter"
结果都将是过滤器的返回值。
全局过滤器:
如果希望在多个组件之间共享过滤器,使用Vue.filter(过滤器名称,过滤器函数)创建全局过滤器。
example:
<template>
<div>
<h1>{{ msg | testFilter}}</h1>
<h1>{{ msg | test}}</h1>
</div>
</template>
<script>
import Vue from 'vue';
export default {
props: {
msg: String
},
filters:{
testFilter(msg){
return msg.toUpperCase()
}
}
}
Vue.filter('test',()=>{
return 'test'
})
</script>
串联过滤器(过滤器链):
{message | filterA | filterB | filterC}
Watch
watch监听器可以监听数据变化,做出对应的操作。
定义方式
- 方法格式监听器
- 无法在加载完页面后自动触发
- 不能够侦听对象的属性
- 对象格式监听器
- 可以通过配置immediate参数在页面加载完成后自动触发
- 配置deep来深度监听
example:
<template>
<div class="hello">
<div class="container">
<input type="text" v-model="username">
<div class="tip" v-show="check"> 数据不合法</div>
<input type="password" v-model="password">
<input type="submit" value="Login ">
</div>
</div>
</template>
<script>
export default {
data() {
return {
check: false,
username: 'example',
password: '',
}
},
watch: {
username(newValue, oldValue) {
if (newValue.length < 6)
this.username = oldValue
}
}
}
</script>
watch: {
// 对象写法
password:{
handler(newValue, oldValue){
console.log(newValue,oldValue);
},
immediate:true //自动触发
}
}
Axios
一个专注于网络请求的库
巧用async/await 和 解构赋值,简化数据读取
<template>
<div class="hello">
<button @click="getData"> get data </button>
<br>
{{ page }}
{{ total }}
<br>
<img class="avatar" v-for="item,index in data" :key="index" :src="item.avatar"/>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
page: 0,
total: 0,
data: [],
}
},
methods:{
async getData(){
let { data:{page, total,data} } = await axios.get('https://reqres.in/api/users?page=2')
// 重命名 data 到 res防止混乱
// let { data:res } = ...
this.data = data
this.page = page
this.total = total
}
},
}
</script>
<style scoped>
.hello {
padding: 10px;
height: 100%;
}
.avatar{
height: 60px;
width: 60px;
object-fit: cover;
margin-left: 10px;
border-radius: 30px;
}
</style>
SPA
SPA是指单页面应用程序 (Sinple Page Application)
指的是web网站中只有一个唯一的html页面
Vue-cli
vue-cli是Vue.js的开发标准工具,它简化了webpack创建工程化Vue项目的过程。
scoped
scoped的情况下修改子组件样式,通过/deep/ 来指定让子组件也生效
/deep/ .el-button{
...
}
LifeCycle
- 创建阶段
- before create
- created
- beforeMount
- mounted
- 运行阶段
- beforeUpdate
- updated
- 销毁阶段
- beforeDestroy
- destroyed
Props
自定义属性,父 > 子传递数据
通过props可以很方便的从父组件的数据传递到子组件,但是传递的值是只读的,子组件的更改不会传递到父组件。
传递对象时,仍然可以修改该对象的属性到父组件 不推荐使用,违背设计原则
$emit
通过自定义事件 可以完成数据的子 > 父的传递
#在子组件中调用$emit('事件名',参数)
#在父组件中通过 v-on:事件名 @事件名 ="接收数据的函数"来接收数据完成对应的操作
example
#子组件
...
this.$emit('sendMsg',this.tempMsg)
...
#父组件
<chat-send-component class="input" @sendMsg="sendMsg"></chat-send-component>
...
methods:{
sendMsg(msg){
this.records.push(msg)
}
}
EventBus
通过单独创建Vue实例,并通过$emit和$on来发送和接收数据,以此来完成兄弟组件之间的数据传递。
这种方式称为EventBus
Array
- forEach((item, index) => {})
forEach方法用于遍历数组中的每一个元素并执行提供的回调函数。它不返回任何值。
- some((item) => { return boolean })
some方法用于检查数组中是否至少有一个元素满足提供的测试函数。如果有一个元素满足条件,则返回true,否则返回false。
- every((item) => { return boolean })
every方法用于检查数组中的所有元素是否都满足提供的测试函数。如果所有元素都满足条件,则返回true,否则返回false。
- reduce((accumulator, currentValue) => { return newValue }, initialValue)
reduce方法用于对数组中的每个元素执行一个由您提供的归约函数(从左到右),将其结果汇总为单个返回值。它接收两个参数:归约函数和初始值。
- filter((item) => { return boolean })
filter方法用于创建一个新数组,其包含所有通过提供的测试函数的元素。
- map((item) => { return newValue })
- 创建一个新数组,其中每个元素是调用提供的回调函数后的返回值。
Vue Router
redirect 重定向 (在路由规则中配置)
{
path:"/",redirect:"/home"
}
如何配置嵌套路由
{
path:"/",component:HomePage,
children:[
...子级路由规则
]
}
默认子路由
如果子路由中某个路由规则的path是空字符串,那么访问到上级路由时后触发该路由规则
这种规则成为默认子路由
追加模式跳转
<router-link :to="{path:'child1',append:true}">inner chat</router-link>
路径参数
在path中使用英文冒号定义动态参数
# 1
{
path: '/movie/:id',
component: MoviePage
}
# 2
{
path: '/movie/:id',
component: MoviePage
props: true
}
接收路径参数
# 1
{{ $route.params.id }}
# 2
props:['id']
查询参数
通过 ?key:value 传递
通过$route.query.key 接收
$route还提供了fullPath(包含查询参数)和path(不包含)
编程式导航
$router.push() 跳转到指定的地址,并增加一条历史记录
$router.replace() 跳转到指定的地址,并替换掉当前的地址(不可以back退回)
$router.go(Number) 前进或者后退指定数量的地址
$router.back()/forward() 后退或者前进一次
全局前置守卫
router.beforeEach((to, from, next)=>{
// to 是将要访问的路由的信息对象
// from 是将要离开的路由的信息对象
// next 是一个函数,调用 next() 表示放行,允许这次路由导航
// next('/login') 改变目标地址并放行
// next(false) 拒绝本次跳转
})
ElementUI
安装
pnpm i 'element-ui'
自动引入
### 安装如下依赖,必须限定版本
pnpm i unplugin-vue-components@0.25.2
pnpm i unplugin-auto-import@0.16.1
配置cli
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {ElementPlusResolver} = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true, configureWebpack: {
plugins: [AutoImport({
resolvers: [ElementPlusResolver()],
}), Components({
resolvers: [ElementPlusResolver()],
}),],
}
})
至此,自动引入配置完成,但是某些组件如图标可能还需要手动引入。
VueX
概念
vuex是为vue而生的状态管理工具
概念:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
npm install vuex
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
基本使用
1.安装
npm i vuex@^3 #限定安装版本为3,供vue2使用,否则将安装最新版
2.配置
在根目录中建立一个
store文件夹来管理状态,而后在main.js中将其全局引入。
import Vue from 'vue'
import Vuex from 'vuex'
import { getItem, setItem } from '@/utils/storage-util'
Vue.use(Vuex)
// actions 用于响应组件中的动作
const actions = {
login (context, user) {
context.commit('setLoginUser', user)
},
logout (context) {
context.commit('setLoginUser', '')
}
}
// mutations 用于操作state
const mutations = {
setLoginUser (state, user) {
state.loginUser = user
setItem('loginUser', user)
}
}
// state 用于存储数据
const state = {
loginUser: getItem('loginUser')
}
// getter 用于获取数据
const getters = {
getLoginUser (state) {
return state.loginUser
},
isLogin (state) {
return !!state.loginUser
}
}
export default new Vuex.Store({
actions: actions,
mutations: mutations,
state: state,
getters: getters
})
3.引入
import store from '@/store'
new Vue({
store,
render: (h) => h(App)
}).$mount('#app')
读取全局状态的方式
// 1
computed: {
user () {
return this.$store.state.loginUser
}
}
// 2
computed: {
...mapState(['loginUser'])
},
// 3
computed: {
...mapState({
user: 'loginUser'
})
},
修改全局状态
1.组件通过dispatch调用actions
2.actions调用mutations
3.mutations最终操作state (mutation不支持异步。)
Modules
在基本使用的方式中,项目中的所有组件都共享维护一个对象,这样显然不够优雅,在Vuex中提供了modules的属性,通过模块的方式,将对象按照一定规则进行拆分,让结构更加清晰!
使用方式(基于原来的方式)
将原来的 state、mutations、actions、getters拆分出来,并按模块归类
并为模块命名放到modules中,即完成了模块的拆分
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
对于模块内部的 action,局部状态通过
context.state暴露出来,根节点状态则为context.rootState:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
命名空间 模块动态注册 模块重用
感觉用不上,过了以后再学。
大型项目最佳实践:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
严格模式
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
const store = new Vuex.Store({
// ...
strict: true
})
Vue3
生命周期
Vue3中引入了组合式API,生命周期发生了改变
vue2 vue3
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

各个生命周期执行时刻,如上所示
template
Vue 3能够支持组件模板具有多个根元素
不再像vue2一样,只能有一个根元素
<script setup>
在 setup() 函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码。
组件之间的通信
- props
父=>子传的为不可变数据 - 自定义事件
子=>父 - 全局事件总线
任意组件mitt实现 - v-model
父<=>子写在组件标签上,实现props和自定义事件 - ref
子=>父子组件需expose可变数据
$parent父=>子父组件需expose可变数据 - provide & inject
父=>子=>孙 - pinia
任意组件vue3中官方推出的任意组件通信方案,有composition & option 写法 - 插槽
父<=>子根据需要使用不同的 slot 类型
Vite
新一代构建工具,突出一个快
优化chunk文件
使用
npm create vite@latest
yarn create vite
# 按照提示信息创建即可
环境变量和模式
Vite 在一个特殊的 import.meta.env 对象上暴露环境变量,这些变量在构建时会被静态地替换掉。
.env 文件
Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量:
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
请注意,如果想要在环境变量中使用 $ 符号,则必须使用 \ 对其进行转义。
KEY=123
NEW_KEY1=test$foo # test
NEW_KEY2=test\$foo # test$foo
NEW_KEY3=test$KEY # test123
要想使用在项目中使用,VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。
VITE_SOME_KEY=123
配置自动打开浏览器
"scripts": {
"dev": "vite --open",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
配置src路径别名
注意Vue: Cannot find module node:path or its corresponding type declarations
请更新webstorm到24版本(坑真多)
1.配置vite.config.ts
import { resolve } from 'node:path'
export default defineConfig({
resolve: {
alias: {
// @ 替代为 src
'@': resolve(__dirname, 'src'),
// @component 替代为 src/component
'@components': resolve(__dirname, 'src/components'),
},
},
})
2.配置tsconfig.app.json
{
"compilerOptions": {
/* path */
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@components": ["src/components/*"],
}
}
}
配置完成后如果还报错可以尝试重启webstorm语言服务,或者直接重启webstorm
SVG插件
安装
pnpm i -D vite-plugin-svg-icons配置vite
plugins: [ createSvgIconsPlugin({ // 指定需要缓存的图标文件夹 iconDirs: [resolve(__dirname, 'src/assets/icons')], // 指定symbolId格式 symbolId: 'icon-[dir]-[name]', }), ],配置main.ts
import 'virtual:svg-icons-register'在/src/assets/icons文件夹中保存svg文件
在组件中使用
<svg style="width: 20px; height: 20px;"> <use xlink:href="#icon-tool" /> </svg>
Eslint
为项目配置Eslint工具
"lint": "eslint .",
"lint:fix": "eslint . --fix"
然后回答对应的问题即可
记录一下安装eslint的依赖时遇到的问题:
ERR_PNPM_EPERM EPERM: operation not permitted...
解决方案:关闭vscode或者webstorm,解除对文件的占用,重新执行。
Eslint最佳实践
pnpm dlx @antfu/eslint-config@latest
配置webstorm自动修复
在设置中开启自动监测eslint,并开启 lint on save即可拥有完美开发体验。
Husky
通过以上配置,能够约束我们自己开发时,保证自己的代码质量,但是在疏忽的时候,仍然可能将不符合规范的代码提交到远程仓库中去,且其他项目成员也可能会提交不符合规范的代码
为了解决以上问题,可以通过husky,对git的操作进行扩展(hook),使用对应的钩子,便可以实现在提交代码之前对项目进行格式或者eslint的修复操作
安装husky
npx husky init执行完成后修改 /.husky/pre-commit
pnpm run lint:fix提交代码到git,自动修复存在的问题
commit lint
检查代码提交的信息,符不符合标准
| 类型 | 描述 |
|---|---|
| build | 发布版本 |
| chore | 改变构建流程、或者增加依赖库、工具等 |
| ci | 持续集成修改 |
| docs | 文档修改 |
| feat | 新特性 |
| fix | 修改问题 |
| perf | 优化相关,比如提升性能、体验 |
| refactor | 代码重构 |
| revert | 回滚到上一个版本 |
| style | 代码格式修改 |
| test | 测试用例修改 |
Pnpm
快速的,节省磁盘空间的包管理工具
相比npm和yarn,他在处理依赖包,和安装速度上有很大的优势
- 安装
npm i -g pnpm
- 设置依赖存储位置
pnpm config set store-dir D:\\tool\\.pnpm-store
- 创建项目
pnpm create vite
- 限制项目包管理工具
- 配置script脚本,判断是否是pnpm,否则就退出
- 配置执行install之前运行该脚本(preinstall)
Mock
通过拦截axios请求伪造数据。
安装
pnpm i -D vite-plugin-mock@2.9.6 mockjs配置 vite
viteMockServe({ localEnabled: command === 'serve', }),创建mock文件夹,创建index.ts
import type { MockMethod } from 'vite-plugin-mock' const arr: any = [] for (let index = 0; index < 20; index++) { arr.push({ customer_name: 'wade', status_text: '登录成功', os: 'Windows 10', browser: 'Chrome(99.0.4844.51)', ip: '192.168.9.110', created: '2021-12-14 10:41:02', location: '局域网 局域网', }) } const logList = { total: 31, page: 1, page_size: 20, list: arr, } const statusList = { data: [ { label: '全部', value: 0 }, { label: '待审核', value: 1 }, ], } export default [ { url: '/mock/api/getList', method: 'post', response: () => { return logList }, }, { url: '/mock/api/getStatusList', method: 'get', response: () => { return statusList }, }, ] as MockMethod[]测试axios
pnpm i axios编写请求
axios.get('/mock/api/getStatusList').then((res) => { console.log(res) })查看控制台
{"data":[{"label":"全部","value":0},{"label":"待审核","value":1}]}
Axios
axios:前端网络请求工具
基本使用
安装
pnpm i axios封装
// 一般放在src的util目录下的request文件夹中 import axios from 'axios' const request = axios.create({ // 通过环境变量读取api配置 baseURL: import.meta.env.VITE_BASE_URL, timeout: 5000, }) export default request创建API
import request from '@/util/request' export function login(username: string, password: string) { // console.log(username, password) return request.post('/login', { username, password }) }
拦截器
用于在请求前后做统一操作。
// 在请求前,执行的操作 通常用于放置token
request.interceptors.request.use((config) => {
config.headers.token = '####test'
return config
})
request.interceptors.response.use((response) => {
// 成功回调
return response
}, (error) => {
// 失败回调
const status = error.response.status
let message = ''
switch (status) {
case 401:
message = '登录过期,请重新登录。'
break
case 403:
message = '权限不足,无法访问。'
break
case 500:
message = '服务器内部错误'
break
default:
message = '网络异常'
}
ElMessage({
type: error,
message,
})
return Promise.reject(error)
})
Pinia
安装
pnpm i pinia
引入
app.use(createPinia())
创建store option
import {defineStore} from "pinia";
interface counterStore {
count: number
}
export const useCounterStore = defineStore('counter', {
state: (): counterStore => ({
count: 0
}),
getters: {
double: (state) => 2 * state.count
},
actions: {
increment() {
this.count++
},
},
})
使用
<template>
<div class="container">
<div class="count">
count: {{counterStore.count}}
</div>
<el-button size="default" type="primary" @click="counterStore.increment()">Increment</el-button>
</div>
</template>
<script setup lang="ts">
import {useCounterStore} from "../../store";
const counterStore = useCounterStore()
</script>
创建store setup
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
// getters
const doubleCount = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
Element Plus
安装
pnpm install element-plus
自动引入
npm install -D unplugin-vue-components unplugin-auto-import
vite配置
// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
// ...
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
通过配置自动引入可以将打包文件体积缩小,减少无用的代码。
问题
Elmessage的样式没有自动引入,无法正常展示
解决方案,在app.vue引入
import 'element-plus/lib/components/message/style/css'


提供强力驱动