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'
}
复制代码