该项目技术选型
不管是vue2还是vue3, 组件通信方式很重要, 不管是项目还是面试都是经常用到的知识点
比如: vue2组件通信方式
props:可以实现父子组件通信, props数据还是只读的!
props可以省略前面的名字
在 Vue.js 中,props 是用来从父组件向子组件传递数据的机制。当我们在子组件中使用 props 接收数据时,实际上接收到的是一个代理对象。这是因为 Vue.js 在进行组件渲染时会对 props 进行响应式处理(reactive),从而可以实现在父组件中更新 props 数据时,自动更新子组件的显示内容。
具体来说,当我们在父组件中更新 props 数据时,实际上会触发一个更新副作用(update effect),从而重新渲染子组件。此时,Vue.js 会重新创建一个代理对象,并将最新的 props 数据绑定到该代理对象上。然后,将该代理对象作为参数传递给子组件,并在子组件中使用该代理对象进行数据的读取和更新。
这种设计方式可以使得我们在子组件中使用 props 数据时,就像使用普通的响应式数据一样方便和灵活。我们可以通过直接读取代理对象属性或者使用计算属性、监听器等方式对 props 数据进行操作,而无需关心该数据的更新和同步,这些都由 Vue.js 内部自动完成。
因此Vue.js 中的 props 采用代理对象的方式进行实现,既可以实现组件间数据的有效传递,又能够保证数据的响应式特性,是 Vue.js 框架中非常重要的一个特性。
父组件给子组件传递数据
子组件获取父组件传递数据:方式①
let props = defineProps({
info:{
type:String,//接受的数据类型
default:'默认参数',//接受默认数据
},
money:{
type:Number,
default:0
}})
方式②
let props = defineProps(["info",'money']);
⭕子组件获取到props数据就可以在模板中使用了,但是切记props是只读的(只能读取,不能修改)
在vue框架中事件分为两种: 一种是原生的DOM事件,另外一种自定义事件。原生DOM事件可以让用户与网页进行交互,比如cick、dbclick、change、 mouseenter、mouseleave...自定义事件可以实现子组件给父组件传递数据。
我是祖国的老花骨朵
pre 是一个表示预格式化文本的标签,用于以固定宽度的字体显示文本内容,其中所有的空格和换行符都会被保留并原样显示,而不会被忽略或被合并。
与 pre 标签相对应的是另外一个 HTML5 标签:code 标签。code 标签也可以用来表示计算机程序代码等预格式化文本,但是它不会保留所有空格和换行符,只会将文本中的特殊字符进行转义,并以默认的字体显示文本内容。因此,code 标签通常用于短小的代码片段,而 pre 标签则适用于较长的代码或其他复杂的预格式化文本内容。
当前代码级给pre标签绑定原生DOM事件点击事件,默认会给事件回调注入event事件对象。当然点击事件想注入多个参数可以按照下图操作。但是切记注入的事件对象务必叫做$event。
我要传递多个参数
在vue3框架click、dbclick、change(这类原生DOM事件),不管是在标签、自定义标签上(组件标签)都是原生DOM事件。
自定义事件可以实现子组件给父组件传递数据.在项目中是比较常用的。
比如在父组件内部给子组件(Event2)绑定一个自定义事件。
在Event2子组件内部触发这个自定义事件
我是子组件2
我们会发现在script标签内部,使用了defineEmits方法,此方法是vue3提供的方法,不需要引入直接使用。defineEmits方法执行,传递一个数组,数组元素即为将来组件需要触发的自定义事件类型,此方执行会返回一个$emit方法用于触发自定义事件。
当点击按钮的时候,事件回调内部调用$emit方法去触发自定义事件,第一个参数为触发事件类型,第二个、三个、N个参数即为传递给父组件的数据。
需要注意的是:代码如下
正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件了
let $emit = defineEmits(["xxx",'click']);
全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this,那么在Vue3想实现全局事件的总线功能就有点不现实啦,如果想在Vue3中使用全局事件总线功能可以使用插件mitt实现。
mitt 通过创建一个事件总线(event bus)来实现事件的订阅和发布。我们可以通过 on() 方法向总线订阅一个事件,并定义该事件被触发时执行的回调函数;也可以使用 emit() 方法向总线发布一个事件,并传递相应的数据参数。mitt 还支持 off() 方法用于取消事件的订阅,并支持 once() 方法用于一次性订阅某个事件。
// 导入 mitt 模块 import mitt from 'mitt' // 创建一个事件总线实例 const bus = mitt() // 向总线订阅一个事件 bus.on('foo', (data) => { console.log('Foo event triggered with data:', data) }) // 向总线发布一个事件 bus.emit('foo', { bar: 'baz' }) // 输出:Foo event triggered with data: { bar: 'baz' } // 取消事件的订阅 bus.off('foo') // 一次性订阅某个事件 bus.once('bar', () => { console.log('Bar event triggered once.') }) // 其他高级使用方式,请参考 mitt 的官方文档
v-model指令: 收集表单数据,数据双向绑定。
v-model也可以实现组件之间的通信,实现父子组件数据同步的业务。
v-model指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步。
而v-model实指利用props[modelValue]与自定义事件[update:modelValue]实现的。
下方代码:相当于给组件Child传递一个props(modelValue)与绑定一个自定义事件update:modelValue
实现父子组件数据同步
在vue3中一个组件可以通过使用多个v-model,让父子组件多个数据同步,下方代码相当于给组件Child传递两个props分别是pageNo与pageSize,以及绑定两个自定义事件update:pageNo与update:pageSize实现父子数据同步。
vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件!
useAttrs 是 Vue.js 3 中的一个新特性,用于在组件中获取所有传递给组件的非响应式属性(non-reactive props)。与 props 属性不同,useAttrs 返回的是一个对象,包含了所有未声明为 props 的属性。这些属性通常被称为“剩余属性”(rest attributes)或“未知属性”(unknown attributes),因为它们在组件内部可能不会被直接使用,而是被传递到其他子组件或用于样式类等。
在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),次函数功能类似于Vue2框架中$attrs属性与$listeners方法。
比如:在父组件内部使用一个子组件my-button
子组件内部可以通过useAttrs方法获取组件属性与事件.因此你也发现了,它类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值。
props与useAttrs方法都可以获取父组件传递过来的属性与属性值。
但是props接受了useAttrs方法就获取不到了。
ref 提及到 ref 可能会想到它可以获取元素的DOM或者获取子组件实例的VC。既然可以在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的。
比如:
在父组件挂载完毕获取组件实例
父组件内部代码:
ref与$parent
但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,因为vue3中组件内部的数据对外“关闭的”,外部不能访问。
$parent 可以获取某一个组件的父组件实例VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过 defineExpose 方法对外暴露。
provide[提供]
inject[注入]
vue3提供两个方法 provide 与 inject, 可以实现隔辈组件传递参数。
组件组件提供数据:
provide方法用于提供数据,此方法执需要传递两个参数, 分别提供数据的key与祖先组件提供数据value。
后代组件可以通过 inject 方法获取数据,通过 key 获取存储的数值。
pinia官网:https://pinia.web3doc.top/**https://pinia.web3doc.top/**
pinia也是集中式管理状态容器,类似于vuex。但是核心概念没有mutation、modules,使用方式参照官网。
插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信.
1️⃣默认插槽:
在子组件内部的模板中书写slot全局组件标签
在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件
注意 :开发项目的时候默认插槽一般只有一个。
2️⃣具名插槽:
顾名思义,此插槽带有名字在组件内部留多个指定名字的插槽。
下面是一个子组件内部,模板中留两个插槽。
todo
父组件内部向指定的具名插槽传递结构。需要注意 v-slot:可以替换为 #
slot
//可以用#a替换
填入组件A部分的结构
//可以用#b替换
填入组件B部分的结构
3️⃣作用域插槽
作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)
子组件Todo代码如下:
todo
-
父组件内部代码如下:
slot
{{$row.title}}
一个项目要有统一的规范,需要使用 eslint + stylelint + prettier 来对我们的代码质量做检测和修复,需要使用 husky 来做 commit 拦截,需要使用 commitlint 来统一提交规范,需要使用 preinstall 来统一包管理工具。
1.环境准备
2.初始化项目
本项目使用vite进行构建,vite官方中文文档参考:cn.vitejs.dev/guide/
pnpm:performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”。
//package.json中
"dev": "vite --open",
eslint中文官网:http://eslint.cn/**http://eslint.cn/**
ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具。
安装eslint
pnpm i eslint -D
生成配置文件:.eslint.cjs
npx eslint --init
module.exports = {
//运行环境
"env": {
"browser": true,//浏览器端
"es2021": true,//校验语法 es2021
},
//规则继承
"extends": [
//全部规则默认是关闭的,这个配置项开启推荐规则,推荐规则参照文档
//比如:函数不能重名、对象不能出现重复key
"eslint:recommended",
//vue3语法规则
"plugin:vue/vue3-essential",
//ts语法规则
"plugin:@typescript-eslint/recommended"
],
//要为特定类型的文件指定处理器
"overrides": [
],
//指定解析器:解析器
//Esprima 默认解析器
//Babel-ESLint babel解析器
//@typescript-eslint/parser ts解析器
"parser": "@typescript-eslint/parser",
//指定解析器选项
"parserOptions": {
"ecmaVersion": "latest",//校验ECMA最新版本
"sourceType": "module"//设置为"script"(默认),或者"module"代码在ECMAScript模块中
},
//ESLint支持使用第三方插件。在使用插件之前,您必须使用npm安装它
//该eslint-plugin-前缀可以从插件名称被省略
"plugins": [
"vue",
"@typescript-eslint"
],
//eslint规则
"rules": {
}
}
vue3环境代码校验插件
# 让所有与prettier规则存在冲突的Eslint rules失效,并使用prettier进行代码检查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
# 运行更漂亮的Eslint,使prettier规则优先级更高,Eslint优先级低
"eslint-plugin-prettier": "^4.2.1",
# vue.js的Eslint插件(查找vue语法错误,发现错误指令,查找违规风格指南
"eslint-plugin-vue": "^9.9.0",
# 该解析器允许使用Eslint校验所有babel code
"@babel/eslint-parser": "^7.19.1",
vue3 环境代码校验插件
# 让所有与prettier规则存在冲突的Eslint rules失效,并使用prettier进行代码检查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
# 运行更漂亮的Eslint,使prettier规则优先级更高,Eslint优先级低
"eslint-plugin-prettier": "^4.2.1",
# vue.js的Eslint插件(查找vue语法错误,发现错误指令,查找违规风格指南
"eslint-plugin-vue": "^9.9.0",
# 该解析器允许使用Eslint校验所有babel code
"@babel/eslint-parser": "^7.19.1",
安装指令
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
1.2修改.eslintrc.cjs配置文件
// @see https://eslint.bootcss.com/docs/rules/
安装指令
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node esli
修改.eslintrc.cjs配置文件
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', // 防止
在src文件夹目录下创建一个index.ts文件:用于注册components文件夹内部全部全局组件!!!
import SvgIcon from './SvgIcon/index.vue';
import type { App, Component } from 'vue';
const components: { [name: string]: Component } = { SvgIcon };
export default {
install(app: App) {
Object.keys(components).forEach((key: string) => {
app.component(key, components[key]);
})
}
}
在入口文件引入src/index.ts文件,通过app.use方法安装自定义插件
import gloablComponent from './components/index';
app.use(gloablComponent);
我们目前在组件内部已经可以使用scss样式,因为在配置styleLint工具的时候,项目当中已经安装过sass sass-loader,因此我们再组件内可以使用scss语法!!!需要加上lang="scss"
接下来我们为项目添加一些全局的样式
在src/styles目录下创建一个index.scss文件,当然项目中需要用到清除默认样式,因此在index.scss引入reset.scss
@import reset.scss
在入口文件引入
import '@/styles'
但是你会发现在src/styles/index.scss全局样式文件中没有办法使用$变量.因此需要给项目中引入全局变量$.
在style/variable.scss创建一个variable.scss文件!
在vite.config.ts文件配置如下:
export default defineConfig((config) => {
css: {
preprocessorOptions: {
scss: {
javascriptEnabled: true,
additionalData: '@import "./src/styles/variable.scss";',
},
},
},
}
}
@import "./src/styles/variable.less";
后面的;
不要忘记,不然会报错!
配置完毕你会发现scss提供这些全局变量可以在组件样式中使用了!!
安装依赖: https://www.npmjs.com/package/vite-plugin-mock
pnpm install -D vite-plugin-mock mockjs
mock中配置文件会报错,是因为vite-plugin-mock版本匹配导致的,可以 pnpm i [email protected]
还有pinia最新版也不匹配 可以安装时候 pnpm i [email protected]
createUserList:次函数执行会返回一个数组,数组里面包含两个用户信息。
对外暴露一个数组:数组里面包含两个接口
登录假的接口
获取用户信息的假的接口
在 vite.config.js 配置文件启用插件。
mport { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
return {
plugins: [
vue(),
viteMockServe({
localEnabled: command === 'serve',
}),
],
}
}
在根目录创建mock文件夹:去创建我们需要mock数据与接口!!!
在mock文件夹内部创建一个user.ts文件
//用户信息数据
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
]
}
export default [
// 用户登录接口
{
url: '/api/user/login',//请求地址
method: 'post',//请求方式
response: ({ body }) => {
//获取请求体携带过来的用户名与密码
const { username, password } = body;
//调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//获取请求头携带token
const token = request.headers.token;
//查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token)
//没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } }
}
//如果有返回成功信息
return { code: 200, data: {checkUser} }
},
},
]
安装axios
pnpm install axios
最后通过axios测试接口!!!
在开发项目的时候避免不了与后端进行交互,因此我们需要使用axios插件实现发送网络请求。在开发项目的时候
我们经常会把axios进行二次封装。
目的:
1:使用请求拦截器,可以在请求拦截器中处理一些业务(开始进度条、请求头携带公共参数)
2:使用响应拦截器,可以在响应拦截器中处理一些业务(进度条结束、简化服务器返回的数据、处理http网络错误)
在根目录下创建utils/request.ts