《硅谷甄选》项目笔记

该项目技术选型

  • Vue3 + 组合式API
  • Vite 构建工具
  • TypeScript
  • vue-router
  • Pinia状态管理

一、vue3组件通信方式

不管是vue2还是vue3, 组件通信方式很重要, 不管是项目还是面试都是经常用到的知识点

比如: vue2组件通信方式

  • props:可以实现父子组件、子父组件、甚至兄弟组件通信
  • 自定义事件:可以实现子父组件通信
  • 全局事件总线$bus:可以实现任意组件通信
  • pubsub:发布订阅模式实现任意组件通信
  • vuex:集中式状态管理容器,实现任意组件通信
  • ref:父组件获取子组件实例VC,获取子组件的响应式数据以及方法
  • slot:插槽(默认插槽、具名插槽、作用域插槽)  实现父子组件通信........

(1)props

props:可以实现父子组件通信, props数据还是只读的! 

props可以省略前面的名字

  • 需要使用到defineProps方法去接受父组件传递过来的数据
  • defineProps是Vue3提供方法,不需要引入直接使用

在 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是只读的(只能读取,不能修改)

(2)自定义事件

在vue框架中事件分为两种: 一种是原生的DOM事件,另外一种自定义事件。原生DOM事件可以让用户与网页进行交互,比如cick、dbclick、change、 mouseenter、mouseleave...自定义事件可以实现子组件给父组件传递数据。

1️⃣原生DOM事件

 
      我是祖国的老花骨朵
 

pre 是一个表示预格式化文本的标签,用于以固定宽度的字体显示文本内容,其中所有的空格和换行符都会被保留并原样显示,而不会被忽略或被合并。

与 pre 标签相对应的是另外一个 HTML5 标签:code 标签。code 标签也可以用来表示计算机程序代码等预格式化文本,但是它不会保留所有空格和换行符,只会将文本中的特殊字符进行转义,并以默认的字体显示文本内容。因此,code 标签通常用于短小的代码片段,而 pre 标签则适用于较长的代码或其他复杂的预格式化文本内容。

当前代码级给pre标签绑定原生DOM事件点击事件,默认会给事件回调注入event事件对象。当然点击事件想注入多个参数可以按照下图操作。但是切记注入的事件对象务必叫做$event。

  
我要传递多个参数

在vue3框架click、dbclick、change(这类原生DOM事件),不管是在标签、自定义标签上(组件标签)都是原生DOM事件。


2️⃣自定义事件

自定义事件可以实现子组件给父组件传递数据.在项目中是比较常用的。

比如在父组件内部给子组件(Event2)绑定一个自定义事件。

 在Event2子组件内部触发这个自定义事件





我们会发现在script标签内部,使用了defineEmits方法,此方法是vue3提供的方法,不需要引入直接使用。defineEmits方法执行,传递一个数组,数组元素即为将来组件需要触发的自定义事件类型,此方执行会返回一个$emit方法用于触发自定义事件。

当点击按钮的时候,事件回调内部调用$emit方法去触发自定义事件,第一个参数为触发事件类型,第二个、三个、N个参数即为传递给父组件的数据。

需要注意的是:代码如下

 正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件了

let $emit = defineEmits(["xxx",'click']);

(3)全局事件总线

全局事件总线可以实现任意组件通信,在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 的官方文档

 (4)v-model

v-model指令: 收集表单数据,数据双向绑定。

v-model也可以实现组件之间的通信,实现父子组件数据同步的业务。

  • 父亲给子组件数据 props
  • 子组件给父组件数据 自定义事件
  • 引入子组件

v-model指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步。

而v-model实指利用props[modelValue]与自定义事件[update:modelValue]实现的。

下方代码:相当于给组件Child传递一个props(modelValue)与绑定一个自定义事件update:modelValue

实现父子组件数据同步

 在vue3中一个组件可以通过使用多个v-model,让父子组件多个数据同步,下方代码相当于给组件Child传递两个props分别是pageNo与pageSize,以及绑定两个自定义事件update:pageNo与update:pageSize实现父子数据同步。

(5)useAttrs

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方法就获取不到了。


(6)ref与$parent

ref 提及到 ref 可能会想到它可以获取元素的DOM或者获取子组件实例的VC。既然可以在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的。

  • ref:可以获取真实的DOM节点,可以获取到子组件实例VC
  • $parent:可以在子组件内部获取到父组件的实例

比如:

在父组件挂载完毕获取组件实例

父组件内部代码:


但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,因为vue3中组件内部的数据对外“关闭的”,外部不能访问。

 $parent 可以获取某一个组件的父组件实例VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过 defineExpose 方法对外暴露。


(7)provide与inject

provide[提供]

inject[注入]

vue3提供两个方法 provide 与 inject, 可以实现隔辈组件传递参数

组件组件提供数据:

provide方法用于提供数据,此方法执需要传递两个参数, 分别提供数据的key与祖先组件提供数据value。

后代组件可以通过 inject 方法获取数据,通过 key 获取存储的数值。


(8)pinia

pinia官网:https://pinia.web3doc.top/**https://pinia.web3doc.top/**

pinia也是集中式管理状态容器,类似于vuex。但是核心概念没有mutation、modules,使用方式参照官网。

(9)slot

插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信.

1️⃣默认插槽:

在子组件内部的模板中书写slot全局组件标签



在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件

注意 :开发项目的时候默认插槽一般只有一个。

2️⃣具名插槽:

顾名思义,此插槽带有名字在组件内部留多个指定名字的插槽。

下面是一个子组件内部,模板中留两个插槽。




父组件内部向指定的具名插槽传递结构。需要注意 v-slot:可以替换为 #




3️⃣作用域插槽

作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)

子组件Todo代码如下:




父组件内部代码如下:






搭建后台管理系统模板

 (1)项目初始化

一个项目要有统一的规范,需要使用 eslint + stylelint + prettier 来对我们的代码质量做检测和修复,需要使用 husky 来做 commit 拦截,需要使用 commitlint 来统一提交规范,需要使用 preinstall 来统一包管理工具。

1.环境准备

  • node v16.14.2
  • pnpm 8.0.0

2.初始化项目

本项目使用vite进行构建,vite官方中文文档参考:cn.vitejs.dev/guide/

pnpm:performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”。

//package.json中
 "dev": "vite --open",

(2)项目配置

1.eslint 配置

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);

3.5 集成sass

我们目前在组件内部已经可以使用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提供这些全局变量可以在组件样式中使用了!!


3.6 mock数据


安装依赖: 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测试接口!!!


3.7 axios二次封装

在开发项目的时候避免不了与后端进行交互,因此我们需要使用axios插件实现发送网络请求。在开发项目的时候

我们经常会把axios进行二次封装。

目的:

1:使用请求拦截器,可以在请求拦截器中处理一些业务(开始进度条、请求头携带公共参数)

2:使用响应拦截器,可以在响应拦截器中处理一些业务(进度条结束、简化服务器返回的数据、处理http网络错误)

在根目录下创建utils/request.ts

你可能感兴趣的:(vue.js,javascript,前端)