简单介绍
想着Vue2在今年年底也要停止维护了,是时候跟进学学Vue3更新下技术栈了。以笔记方式整理一下学习过程中的要点,顺便巩固一下基础知识。
一些我已经看过的内容链接:
- Vue3官方互动教程
去掉开始和结束的步骤,有13个步教程,能够比较快地了解Vue3的一些基本知识,并且动手实践,左上角可以切换到组合式API,体验Vue3的新特性
- 面试官问:vue2和vue3的区别有哪些?
如果你已经有学习Vue2的经验,可以了解一下它和Vue3的区别
余下的笔记内容主要整理自B站视频:尚硅谷Vue项目实战硅谷甄选,vue3项目+TypeScript前端项目一套通关(B站大学好:joy:)
组件通信方式
- props
- 自定义事件:主要是子组件到父组件传递数据,父组件使用v-on/@绑定事件,子组件使用emit
- 全局事件总线Bus:适用于任意组件间通信
- 消息订阅与发布pubsub:需要引入pubsub-js依赖
- vuex
- ref:父组件取得子组件实例,从而使用子组件数据和方法
- slot:插槽
几种通信模式的介绍和例子:Vue组件之间通信(props、自定义事件、全局事件总线、消息订阅与发布、Vuex)
props
在子组件使用defineProps
定义props,使得父组件可以利用v-bind/:向子组件传递只读参数
自定义事件
在子组件使用defineEmits
定义emit,使得父组件v-on/@绑定的为自定义事件,而非DOM原生事件
全局事件总线Bus
相比于Vue2,Vue3无法将自身实例挂载在Vue原型上、组合式函数setup
中也无法使用this
,因此无法直接使用$bus,可以利用mitt依赖实现。
可以构造一个js脚本,暴露mitt提供的bus实例,供其他组件引入使用
import mitt from 'mitt'
const $bus = mitt()
export default $bus
组件传递数据时,可以利用如下的方式实现
import $bus from '@/bus'
const handler => () => {
$bus.emit('obj', {value: 'value'})
}
组件接收数据时,可以利用如下的方式实现
import $bus from '@/bus'
import {onMounted} from 'vue'
onMounted(() = > {
$bus.on('obj', obj => {
console.log(obj)
})
})
v-model
同步单个属性
v-model="data"
在Vue3中相当于:modelValue="data"
和@update:modelValue="handler"
的组合。
update:modelValue
为emit方法的自定义事件名称;handler
为父组件用于更新属性data
值的方法。
稍微回顾一下Vue2中v-model的使用,v-model相当于
:value
和@input
的组合
同步多个属性
例如,组件需要接收pageNum和pageSize的参数,并对参数进行同步,则可以将v-model写成这样的形式:v-model:pageNum="pageNum" v-model:pageSize="pageSize"
,子组件的emit事件名称则对应为update:pageNum
和update:pageSize
。
useAttrs
使用useAttrs可以接收父组件传入的属性而无需列出具体名称,props的优先级更高,若属性已经在props内定义,则useAttrs取到的对象内无该属性。
这里以对ElementUI-Plus的按钮组件进行封装为例:
<template>
<el-button :="$attrs" />
</template>
<script setup>
import {useAttrs} from 'vue'
const $attrs = useAttrs()
// 若要取得传入的title参数,可以使用$attrs.title取得
// $attrs内也可取得事件方法
</script>
$ref和$parent
ref
对于子组件,可以使用defineExpose
暴露已定义的属性和方法,供其他组件调用
<script setup>
import {ref} from 'vue'
let money = ref(666)
const handler = () => {
console.log('子组件方法')
}
// 对外暴露money属性和handler方法
defineExpose({
money,
handler
})
</script>
对于父组件,可以获取和使用子组件已经暴露出的属性和方法
<template>
<div>
<childComp ref="child"></childComp>
</div>
</template>
<script setup>
import ChildComp from './childComp'
import {ref, onMounted} from 'vue'
let child = ref(null); // 获取子组件实例,变量名要与ref属性值一致
const onMounted = () => {
console.log(child.value.money) // 输出子组件内的属性
child.value.hadler()
}
</script>
parent
对于子组件,在执行事件时传入$parent
对象,即可通过$parent
取得父组件实例
<template>
<button @click="handler($parent)"></button>
</template>
<script setup>
const handler = ($parent) => {
console.log($parent)
}
</script>
同样地,父组件需要使用defineExpose
暴露已定义的属性和方法,供其他组件调用,就不再重复举例了。
provide和inject
Vue3提供provide
和inject
方法,可以实现隔辈组件传递数据
在祖先组件中,可以使用如下的方式传递数据,provide
方法的第一个参数是数据的Key,第二个参数为数据
import {ref, provide} from 'vue'
let token = ref('X-TOKEN')
provide("TOKEN", token)
在孙子组件中,可以使用inject
引入数据,传入数据的Key即可
import {inject} from 'vue'
let token = inject('TOKEN')
console.log(token.value)
pinia状态管理
pinia与vuex类似,也是集中式状态管理库,是Vue3中使用的新的状态管理库,可以实现任意组件之间的通信。
中文文档可阅读:Pinia中文文档
复习一下,vuex的核心概念:state、mutations、actions、getters、modules
pinia的核心概念有:state、actions、getters,同样的他也可以模块化使用
创建store/index.ts
import {createPinia} from 'pinia'
const store = createPinia()
export default store
在main.ts中引入store
// ...
import store from './store'
// ...
app.use(store)
// ...
在store/modules/todo.ts中定义modules
import {defineStore} from 'pinia'
import {ref} from 'vue'
const useTodoStore = defineStore('todo', () => {
const todos = ref({
id: 1,
title: '吃饭'
})
const addTodo = item => {
todos.value.push(item)
}
return {
todos,
addTodo
}
})
在组件内使用todoStore可以采用如下的方式
import useTodoStore from '@/store/modules/todo'
let todoStore = useTodoStore()
console.log(todoStore.todos.value)
todoStore.todos.addTodo({
id: 2,
title: '睡觉'
})
插槽
有以下三种类型的插槽:
- 默认插槽
- 具名插槽
- 作用域插槽
默认插槽
在子组件中可以有如下的结构,
<template>
<div class="childComp">
<p>Last para</p>
<slot></slot>
<p>next para</p>
</div>
</template>
父组件要将结构传入子组件内可以以这样的方式,
<template>
<div>
<ChildComp>
<div>Content</div>
</ChildComp>
</div>
</template>
具名插槽
对于子组件,需要利用name
属性定义插槽的名称,
<slot name="a"></slot>
在父组件中向子组件传递结构时,可以写成如下的形式,需要用到v-slot
属性
<template v-slot:a>
<div>填充</div>
</template>
或者可将v-slot
简写成#
<template #a>
<div>填充</div>
</template>
作用域插槽
子组件可以将数据回传给父组件,父组件决定回传的数据以何种结构或外观在子组件内部展示。
传递单个对象
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
传递列表
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>
项目初始化
使用pnpm + Vite初始化项目
npm install -g pnpm
pnpm create vite
其中pnpm和vite对node有版本要求,项目初始化时需要注意
如果在Windows系统下,出现因为在此系统上禁止运行脚本的报错信息
可以使用win + x打开管理员终端,输入set-ExecutionPolicy RemoteSigned
解除限制
运行项目
pnpm run dev
使用eslint
引入eslint用于保证代码质量
安装eslint
pnpm install eslint -D
生成eslint配置文件.eslint.cjs
npx eslint --init
Vue3环境代码校验插件
pnpm i -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
安装插件后,修改eslint校验规则.eslint.cjs
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
/* 继承已有的规则 */
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
}
}
创建.eslintignore
忽略不需要eslint检测的目录
dist
node_modules
package.json
新增运行脚本,分别用于检测和纠正src目录代码
"scripts": {
"lint": "eslint src",
"fix": "eslint src --fix"
}
配置prettier
在保证代码质量的同时,也要保证代码的美观,因此需要引入prettier配合eslint达到效果。
安装相关依赖
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
添加.prettierrc.json
规则
{
"singleQuote": true,
"semi": false,
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"endOfLine": "auto",
"trailingComma": "all",
"tabWidth": 2
}
添加.prettierignore
忽略配置
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
配置styleLint
styleLint为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等。
由于项目中使用scss以及为了使用styleLint,因此需要使用命令添加如下的依赖
pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
配置.stylelintrc.cjs
文件
// @see https://stylelint.bootcss.com/
module.exports = {
extends: [
'stylelint-config-standard', // 配置stylelint拓展插件
'stylelint-config-html/vue', // 配置 vue 中 template 样式格式化
'stylelint-config-standard-scss', // 配置stylelint scss插件
'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化
'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件,
'stylelint-config-prettier', // 配置stylelint和prettier兼容
],
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml',
],
/**
* null => 关闭该规则
* always => 必须
*/
rules: {
'value-keyword-case': null, // 在 css 中使用 v-bind,不报错
'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"
'no-empty-source': null, // 关闭禁止空源码
'selector-class-pattern': null, // 关闭强制选择器类名的格式
'property-no-unknown': null, // 禁止未知的属性(true 为不允许)
'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符
'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box
'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask
'selector-pseudo-class-no-unknown': [
// 不允许未知的选择器
true,
{
ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到
},
],
},
}
配置.stylelintignore
忽略文件
/node_modules/*
/dist/*
/html/*
/public/*
运行脚本配置(在package.json
中)
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
配置husky(待完善)
husky是一个git hook,可以用于在代码提交到git前的检测和处理。
暂未使用,待完善。
配置commitLint(待完善)
commitLint用于规范项目团队开发时的commit信息,进行统一规范。
暂未使用,待完善。
统一包管理器工具(待完善)
利用preinstall脚本,在install前检测使用的包管理工具,避免团队因为使用不同包管理工具照成冲突或报错。
暂未使用,待完善。
集成Element-Plus
似乎有点过于基础了,简单做个笔记就好:cry:
安装Element-Plus及图标库依赖
pnpm install element-plus
pnpm install @element-plus/icons-vue
在main.ts
中引入Element-Plus,并引入中文语言包
// ...
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' // 中文语言包
// ...
const app = createApp(App)
app.use(ElementPlus, {
locale: zhCn
})
app.mount('#app')
在组件中引入单个图标
import {Plus} from '@element-plus/icon-vue'
配置src目录别名
在项目开发过程中会经常用到/scr
这个目录,通常会为其设置@
作为别名,需要进行一定的配置。
配置vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
}
}
})
配置TypeScript编译配置tsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
}
}
}
配置项目环境变量
可以为项目配置环境变量,以适应不同场景的运行情况。
主要有以下三种环境:
- 开发环境
.env.development
- 测试环境
.env.production
- 生产环境
.env.test
环境变量内容示例:
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/api'
在package.json
中配置不同环境的运行命令:
{
"scripts": {
"dev": "vite --open",
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview"
}
}
之后要取得环境变量时,可以使用import.meta.env
读取
配置SvgIcon
SvgIcon的简单使用和封装
先安装svg依赖插件
主要是用于生成svg图标雪碧图,以减少网络请求
pnpm install vite-plugin-svg-icons -D
在vite.config.ts
中配置插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// Specify the icon folder to be cached
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// Specify symbolId format
symbolId: 'icon-[dir]-[name]',
}),
],
}
}
在入口文件main.ts
中引入插件
import 'virtual:svg-icons-register'
将svg图标文件放置在src/assets/icons
目录下,例如要使用apple.svg
图标,可以采用如下的方式简单引入
<!-- svg:图标外层容器,内部需要use标签结合使用 -->
<svg>
<!-- xlink:href执行用哪一个图标,属性值必须加上 #icon-图标名字 -->
<use xlink:href="#icon-apple" fill="red"></use>
</svg>
为了方便后续项目中的多次使用svg图标,可以将其封装为一个SvgIcon组件
<template>
<div>
<svg :style="{ width: width, height: height }">
<use :xlink:href="prefix + name" :fill="color"></use>
</svg>
</div>
</template>
<script setup lang="ts">
defineProps({
//xlink:href属性值的前缀
prefix: {
type: String,
default: '#icon-'
},
//svg矢量图的名字
name: String,
//svg图标的颜色
color: {
type: String,
default: ""
},
//svg宽度
width: {
type: String,
default: '16px'
},
//svg高度
height: {
type: String,
default: '16px'
}
})
</script>
<style scoped></style>
将SvgIcon注册为全局组件
创建src/index.ts
文件,用于注册src/components
目录下的所有全局组件
// 引入项目中全部的全局组件
import SvgIcon from '@/components/SvgIcon/index.vue';
// 全局组件的对象
const allGlobalComponents = { SvgIcon: SvgIcon };
// 对外暴露一个插件对象
export default {
install(app) {
Object.keys(allGlobalComponents).forEach((key) => {
app.component(key, allGlobalComponents[key]);
})
}
}
在入口文件main.ts
中引入刚刚创建的index.js
,并使用use
方法全局引入组件
// 引入自定义插件对象:注册全局组件
import globalComponent from '@/components/index.ts'
// 安装自定义插件
app.use(globalComponent)
在组件中引入单个图标
import {Plus} from ‘@element-plus/icon-vue’
这里写错了,应该是:
import { Plus} from ‘@element-plus/icons-vue’
就为这少一个s,我查了半天的问题