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 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 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

image-20240723091044570

各个生命周期执行时刻,如上所示

template

Vue 3能够支持组件模板具有多个根元素

不再像vue2一样,只能有一个根元素

&lt;script setup&gt;

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的修复操作

  1. 安装husky

    npx husky init
    
  2. 执行完成后修改 /.husky/pre-commit

    pnpm run lint:fix
    
  3. 提交代码到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:前端网络请求工具

基本使用

  1. 安装

    pnpm i axios
    
  2. 封装

    // 一般放在src的util目录下的request文件夹中
    import axios from 'axios'
    
    const request = axios.create({
      // 通过环境变量读取api配置
      baseURL: import.meta.env.VITE_BASE_URL, 
      timeout: 5000,
    })
    
    export default request
    
  3. 创建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'