Vue 开发必须知道的 36 个技巧【近1W字】

本文详细介绍了 Vue 开发中的一些高效技巧,包括使用 require.context 动态批量引入文件、全局注册组件、watch 的深度监听、组件间的 14 种通信方式、异步组件、路由模块化、Vue 指令和过滤器的使用等,旨在提升 Vue 开发效率和代码质量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue 开发必须知道的 36 个技巧【近1W字】

前言

Vue 3.x 的Pre-Alpha 版本。后面应该还会有 Alpha、Beta 等版本,预计至少要等到 2020 年第一季度才有可能发布 3.0 正式版;
所以应该趁还没出来加紧打好 Vue2.x 的基础;
Vue基本用法很容易上手,但是有很多优化的写法你就不一定知道了,本文从列举了 36 个 vue 开发技巧;
后续 Vue 3.x 出来后持续更新.

require.context批量引入文件

require.context 是什么#

require.context 是由webpack内部实现,require.context``在构建时,webpack 在代码中进行解析。

当需要引入文件夹内多个文件模块时,可以使用 require.context 自动化批量引入,而不用手动一条一条添加。

参数#

require.context 函数接收三个参数

  • String 读取文件夹的路径
  • Boolean 是否遍历文件夹的子目录
  • RegExp 匹配文件的正则

如何使用#

用我实际开发的场景来做例子,现在文件夹内有多个 api 文件,我需要将这些组合起来

目录如下:

<api文件夹>

二级 -add.js

二级-edit.js

二级 -del.js

一级api.js

<api文件夹>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5hpOXpJ-1659930118218)(C:\Users\Administrator.SC-202007261508\Desktop\Vue 开发必须知道的 36 个技巧【近1W字】.assets\1597222739471.png)]

api.js

//引入api文件夹下的api接口
let requireAll = require.context('./api', false, /\.js$/)
//requireAll.keys()为文件名数组; requireAll(apiName)获取文件暴露的内容
const apiArr = requireAll.keys().map(apiName=> requireAll(apiName).default || requireAll(apiName))
//组合接口
let api = apiArr.reduce((prev,curr)=> Object.assign(prev,curr), {})

export default api
ruquireAllApi(apiName).default 获取的是Es6规范暴露的内容(如:export default)
ruquireAllApi(apiName) 获取的是CommonJs规范暴露的内容(如:module.exports)

require.context 全局注册通用的基础组件 一

原文链接

参考二

路由-组件 引入

一、全部代码

/// 全局组件自动配置  START
const requireComponent = require.context(
  // 其组件目录的相对路径
  "./views/components",
  // 是否查询其子目录
  true,
  // 匹配基础组件文件名的正则表达式
  /\w.(vue|js)/
);

// For each matching file name...  对应每个匹配的文件名
requireComponent.keys().forEach(fileName => {
  // Get the component config  获取组件配置
  const componentConfig = requireComponent(fileName);

  // 获取组件的 PascalCase 命名
  // 获取和目录深度无关的文件名 (获取组件的组件名)
  const componentName = fileName
    .split("/")
    .pop()
    .replace(/\.\w+$/, "");

  // Globally register the component  全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  );
});
/// 全局组件自动配置  END
1234567891011121314151617181920212223242526272829303132

二、代码解析

2.1、匹配正则表达式

就是获取组件目录下的单文件的方式

const requireComponent = require.context(
  // 其组件目录的相对路径
  "./views/components",
  // 是否查询其子目录
  true,
  // 匹配基础组件文件名的正则表达式
  /\w.(vue|js)/
);
12345678

2.2、通过require.context获取的符合正则表达式的文件通过循环添加到全局组件Vue.component(name,Loading)

requireComponent.keys().forEach(fileName => {})
1

例如:Vue.component(name,Loading)

import Vue from 'vue';
// 引入loading组件 
import Loading from './loading.vue';
// 将loading注册为全局组件,在别的组件中通过<loading>标签使用Loading组件
Vue.component('loading', Loading);
12345

2.3、输出Vue.component(name,in)的name

fileName为一个“./XXX”的字符,而Vue.component(name,Loading) 全局组件的name为“XXX”,没有“./”,所以要用正则去掉.

// 获取和目录深度无关的文件名 (获取组件的组件名)
  const componentName = fileName
    .split("/")
    .pop()
    .replace(/\.\w+$/, "");

#require.context 使用二

主要使用require.context实现前端工程化动他引入文件
require.context(directory, useSubdirectories = false, regExp = /^.//)
第一个参数目标文件夹
是否查找子集 true | false
正则匹配
比如:
require.context('./router',true,/\.routes\.js/
可以理解为获取router文件下以.routes.js结尾的文件,知道这个以后,就可以在项目动态引入文件,方便使用了

vue全局注册组件

在项目中,我们都会针对项目的功能,将项目中高频出现的部分写成组件方便调用,这个时候可以使用require.context完成组件注册,省去在每个页面进行import的工作
比如:我们把组件全部写在components文件夹下,然后创建componentRegister.js使用require.context进行组件注册

function changStr(str){
    return str.charAt(0).toUpperCase()+str.slice(1)  
}
export default {
    install(Vue) {
        const requireAll=require.context("./components",false,/\.vue$/)
        requireAll.keys().forEach((item)=>{
            Vue.component(changStr(item.replace(/\.\//,'').replace(/\.vue$/,'')),requireAll(item).default)
        })
    }
}

然后只要在main.js里引入这个js文件,然后vue.use()注册就可以在所有页面调用组件了
比如在components下创建了HelloWorld.vue组件,在页面中只需要``这样就可以使用了

vue路由模块化 require.context

同理,也可以解决另一个问题,在vue项目中,路由文件会随着项目增大而越来越大,这个这个我们可以使用require.context进行模块化管理,首页定义好主路由,router.js

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);
const routerList = [];
function importAll(r) {
    r.keys().forEach((key) => {
        routerList.push(r(key).default);
    });
}
importAll(require.context("../routes", true, /\.routes\.js/));//这里的目录和规则可以看自己习惯,这里获取的是routes下以.routes.js结尾的文件
const routes = [
    ...routerList,
];

const router = new VueRouter({
    routes,
});

export default router;

这样就可以在routes下面按模块管理路由了,不管加入什么,只需要在routes下新建就可以了

require.context() 导入多个组件 三

可参考

1.场景:如页面需要导入多个组件,原始写法:

import titleCom from '@/components/home/titleCom'
import bannerCom from '@/components/home/bannerCom'
import cellCom from '@/components/home/cellCom'
components:{titleCom,bannerCom,cellCom}
复制代码

2.这样就写了大量重复的代码,利用 require.context 可以写成

const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
  const name = path.basename(key, '.vue')
  modules[name] = files(key).default || files(key)
})
components:modules
复制代码

这样不管页面引入多少组件,都可以使用这个方法

3.API 方法

实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用
require.context(directory,useSubdirectories,regExp)
接收三个参数:
directory:说明需要检索的目录
useSubdirectories:是否检索子目录
regExp: 匹配文件的正则表达式,一般是文件名
复制代码

2.watch

2.1 常用用法

1.场景:表格初始进来需要调查询接口 getList(),然后input 改变会重新查询

created(){
  this.getList()
},
watch: {
  inpVal(){
    this.getList()
  }
}
复制代码

2.2 立即执行

2.可以直接利用 watch 的immediate和handler属性简写

watch: {
  inpVal:{
    handler: 'getList',
      immediate: true
  }
}
复制代码

2.3 深度监听

3.watch 的 deep 属性,深度监听,也就是监听复杂数据类型

watch:{
  inpValObj:{
    handler(newVal,oldVal){
      console.log(newVal)
      console.log(oldVal)
    },
    deep:true
  }
}
复制代码

此时发现oldVal和 newVal 值一样; 因为它们索引同一个对象/数组,Vue 不会保留修改之前值的副本; 所以深度监听虽然可以监听到对象的变化,但是无法监听到具体对象里面那个属性的变化

3. 14种组件通讯

3.1 props

这个应该非常属性,就是父传子的属性; props 值可以是一个数组或对象;

// 数组:不建议使用
props:[]

// 对象
props:{
 inpVal:{
  type:Number, //传入值限定类型
  // type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol
  // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认
  required: true, //是否必传
  default:200,  //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[]
  validator:(value) {
    // 这个值必须匹配下列字符串中的一个
    return ['success', 'warning', 'danger'].indexOf(value) !== -1
  }
 }
}
复制代码

3.2 $emit

这个也应该非常常见,触发子组件触发父组件给自己绑定的事件,其实就是子传父的方法

// 父组件
<home @title="title">
// 子组件
this.$emit('title',[{title:'这是title'}])
复制代码

3.3 vuex

1.这个也是很常用的,vuex 是一个状态管理器 2.是一个独立的插件,适合数据共享多的项目里面,因为如果只是简单的通讯,使用起来会比较重 3.API

state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问
getter:获取 store 值,可认为是 store 的计算属性,可通过this.$store.getter 或
       mapGetters访问
mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,
         vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用
action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions
       访问
modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
复制代码

3.4 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9encPcLh-1659930118223)(https://juejin.im/equation?tex=attrs%E5%92%8C)]listeners

2.4.0 新增 这两个是不常用属性,但是高级用法很常见; 1.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbduAvX6-1659930118224)(https://juejin.im/equation?tex=attrs%0A%E5%9C%BA%E6%99%AF%3A%E5%A6%82%E6%9E%9C%E7%88%B6%E4%BC%A0%E5%AD%90%E6%9C%89%E5%BE%88%E5%A4%9A%E5%80%BC%2C%E9%82%A3%E4%B9%88%E5%9C%A8%E5%AD%90%E7%BB%84%E4%BB%B6%E9%9C%80%E8%A6%81%E5%AE%9A%E4%B9%89%E5%A4%9A%E4%B8%AA%20props%0A%E8%A7%A3%E5%86%B3%3A)]attrs获取子传父中未在 props 定义的值

// 父组件
<home title="这是标题" width="80" height="80" imgUrl="imgUrl"/>

// 子组件
mounted() {
  console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"}
},
复制代码

相对应的如果子组件定义了 props,打印的值就是剔除定义的属性

props: {
  width: {
    type: String,
    default: ''
  }
},
mounted() {
  console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"}
},
复制代码

2.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ErhnLff-1659930118227)(https://juejin.im/equation?tex=listeners%0A%E5%9C%BA%E6%99%AF%3A%E5%AD%90%E7%BB%84%E4%BB%B6%E9%9C%80%E8%A6%81%E8%B0%83%E7%94%A8%E7%88%B6%E7%BB%84%E4%BB%B6%E7%9A%84%E6%96%B9%E6%B3%95%0A%E8%A7%A3%E5%86%B3%3A%E7%88%B6%E7%BB%84%E4%BB%B6%E7%9A%84%E6%96%B9%E6%B3%95%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%20v-on%3D%22)]listeners" 传入内部组件——在创建更高层次的组件时非常有用

// 父组件
<home @change="change"/>

// 子组件
mounted() {
  console.log(this.$listeners) //即可拿到 change 事件
}
复制代码

如果是孙组件要访问父组件的属性和调用方法,直接一级一级传下去就可以

3.inheritAttrs

// 父组件
<home title="这是标题" width="80" height="80" imgUrl="imgUrl"/>

// 子组件
mounted() {
  console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"}
},

inheritAttrs默认值为true,true的意思是将父组件中除了props外的属性添加到子组件的根节点上(说明,即使设置为true,子组件仍然可以通过$attr获取到props意外的属性)
将inheritAttrs:false后,属性就不会显示在根节点上了
复制代码

3.5 provide和inject

2.2.0 新增 描述: provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中; 并且这对选项需要一起使用; 以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

//父组件:
provide: { //provide 是一个对象,提供一个属性或方法
  foo: '这是 foo',
  fooMethod:()=>{
    console.log('父组件 fooMethod 被调用')
  }
},

// 子或者孙子组件
inject: ['foo','fooMethod'], //数组或者对象,注入到子组件
mounted() {
  this.fooMethod()
  console.log(this.foo)
}
//在父组件下面所有的子组件都可以利用inject
复制代码

provide 和 inject 绑定并不是可响应的。这是官方刻意为之的。 然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的,对象是因为是引用类型

//父组件:
provide: { 
  foo: '这是 foo'
},
mounted(){
  this.foo='这是新的 foo'
}

// 子或者孙子组件
inject: ['foo'], 
mounted() {
  console.log(this.foo) //子组件打印的还是'这是 foo'
}
复制代码

3.6 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNaB6TCY-1659930118231)(htt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值