动态导入本地图片
假设我们有这么一个功能,后台返回图片的名称,前端需要自己拼接路径获取本地图片,假定这些资源是存在我们前端的 assets/images
,如果你采取传统的字符串拼接的方式:
//template
<img :src="'@/assets/images' + imgUrl" alt="">
发现图片无法显示,打开控制台审查元素,发现路径并没有正确解析,这个跟 webpack
编译打包有关系,在编译过程中目录结构改变导致的。
我们只需要一个 require
方法就可以完美解决这个问题:
//template
<img :src="require('@/assets/images' + imgUrl)" alt="">
刷新瞧瞧,是不是可以了~
开发和生产的路由配置
配置路由的时候,开发环境下不需要使用 lazy-loading
加载 , 仅在生产环境使用即可,因为开发模式使用 lazy-loading
会导致 webpack
热更新比较慢。
可以创建2个 js
,分别为 _import_development.js
和 _import_production.js
用来加载我们的组件
ps
: 我的页面是在 views
文件夹下
// _import_development.js
module.exports = file => require('@/views' + file + '.vue').default;
// _import_production.js
module.exports = file => () => import('@/views/' + file + '.vue')
之后我们可以在我们的路由文件中通过当前运行环境( process.env.NODE_ENV
)来加载不同的导入文件js
,大概就变成下面的样子了。
// router.js
const _import = require('./_import_' + process.env.NODE_ENV);
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Index',
component: _import('/index')
}
],
mode: 'history'
})
带参数的自定义指令
我们平时书写自定义指令大部分都是以下的这种方式:
Vue.directive('background', {
inserted: function (el) {
// 修改背景色
el.style.backgroundColor = 'red'
}
})
这样可能在某些场景下显得不够灵活,其实我们是可以给指令传递参数的,我们可以将上面的代码改成下面这样:
Vue.directive('background', {
inserted: function (el,binding) {
// 修改背景色
el.style.backgroundColor = binding.value
}
})
其中第二个参数 binding
是一个对象,包含下面这些属性:
-
name
:指令名,不包括v-
前缀。 -
value
:指令的绑定值,例如:v-background="'red'"
中,绑定值为red
。 -
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。 -
expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。 -
arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。 -
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
使用时就可以
//固定值
<div class="" v-background="'red'">Hello World</div>
//动态传值
<div class="" v-background="color">Hello World</div>
其中 color
绑定的是 data
里面的 color
值。
如果你觉得这么还不够灵活,我想动态修改参数可以吗?当然没问题,请看下面:
Vue.directive('style', {
inserted: function (el,binding) {
el.style[binding.arg] = binding.value;
}
})
这么一来,你想修改啥直接写就是啦。
// 修改背景色
<div class="" v-style:[`background`]="'red'">Hello World</div>
如果我想批量修改,so easy
传个对象就好了,如下
Vue.directive('style', {
inserted: function (el,binding) {
for( let key in binding.value){
el.style[key] = binding.value[key]
}
}
})
// 批量修改
<div class="" v-style="{ color : 'white' , background : 'red'}">Hello World</div>
带参数过滤器
过滤器通常在** 双花括号插值**和 v-bind 表达式 中使用,经常是为了来格式化一些文本之类的。
它跟自定义指令一样,也是可以带参数的,不过过滤器比起指令要简单的多。
假设我们需要将后端传过来的时间戳格式化一下,一般的这么写就可以了:
// ps: 这里引入了一个 moment 包
Vue.filter('formatDate',function (val) {
return moment.unix(val).format('YYYY-MM-DD HH:mm:ss')
})
后来为了让用户可以自定义显示格式,后端增加了一个formate
字段,我们不得不修改我们的过滤器,这时候就需要给过滤器加参数,来解决这个问题
Vue.filter('formatDate',function (val,format) {
return moment.unix(val).format(format)
})
调用的时候只需要这么传入即可:
<div class="">{{ timestamp | formatDate('YYYY/MM/DD HH:mm:ss') }}</div>
过滤器第一个参数仍然是原始的值,YYYY/MM/DD HH:mm:ss
作为第二个参数传到了 format
中,这样的拓展性是不是更好了呢~
$attrs解决数据多级传递
$attrs
官方解释是包含了父作用域中不作为 prop
被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop
时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
理解起来一头雾水,其主要意思是父组件往子组件传没有在props
里声明过的值时,子组件可以通过$attrs
接受,且只包含父组件没有在props
里声明的值。
通常我们如果需要从父组件接收传递很多个值,那么我们就需要在 props
里声明需要接受的值,如果孙子组件也需要,那么就又要重复在props
中声明,显得非常繁琐。
例如我们现在有 A
,B
,C
三个组件,是父子孙的关系,如果 B
、C
组件都要从 A
组件中继承一系列的属性:
// com-a
<template>
<com-b :title='title' :desc='desc' :date='date'></com-b>
</template>
// com-b
<template>
<com-c v-bind='$attrs'></com-c>
</template>
这时候我们在 B
组件中通过 $attrs
就可以获取父组件传递的 title
、desc
和date
,此外我们只要给 C
组件绑定 v-bind='$attrs'
, 同理,C
组件内部也就可以通过 $attrs
获取到 A
里面的值了~
ps:
B
、C
组件的 DOM
上会绑定 A
传过来的属性,Vue
内部默认是这么处理的,要去掉的话给 B
、C
组件加上 inheritAttrs : false
属性即可。
$listeners
的用法也比较类似,不赘述了~
跨组件通信的另一种方式
想到跨组件通信,可能会想到 eventBus
, vuex
之类的方法,实际上我们可以借助 vue
本身的依赖注入这种方案优雅实现
我们首先需要在 main.js
中,定义一个 eventHub
, 这是我们的关键点
// main.js
new Vue({
el: '#app',
router,
store,
components: {
App
},
data: {
eventHub: new Vue()
},
template: '<App/>'
})
之后,在我们在需要监控的组件的生命周期中绑定一下:
// com-a
mounted () {
this.$root.eventHub.$on('update',(data)=>{
console.log(data);
})
},
beforeDestroy () {
this.$root.eventHub.$off('update')
}
其他组件要触发改事件只需要一句话:
emitEevent () {
this.$root.eventHub.$emit('update',{ msg : 'hello world' })
}
值得注意的是,一定要在 beforeDestroy
生命周期中通过 $off
取消监听,不然会重复监听导致触发多次,如果只需要触发一次事件的话,$once
绑定会更加不错。
函数式组件
Vue
里的函数式组件和 React
中的无状态组件有些类似,如果说一个组件没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,那么这时候我们可以考虑使用函数式组件。
函数式组件跟普通组件相比,因为没有状态管理,声明周期,只是函数,所以渲染开销低很多,以此可以优化我们的代码。
通常函数式组件的声明方式有2种(局部组件为例):
一种是模版渲染方式加上 functional
关键字创建
<template functional>
/***/
</template>
另一种是通过 render
渲染函数,并加上 functional
属性来标识创建,这种方式比模版更接近编译器,更加底层,渲染会更加迅速。
export default {
functional: true,
// Props 是可选的
props: {
// ...
},
render: function (createElement, context) {
// ...
}
}
关于 render
函数,由于篇幅太长,这边不在赘述,想要了解更多细节和配置参数,可以参考官网的解释
由于函数式组件没有实例,为了弥补这个问题,组件需要的一切都是通过 context
参数传递,它是一个包括如下字段的对象:
-
props
:提供所有prop
的对象 -
children
:VNode
子节点的数组 -
slots
: 一个函数,返回了包含所有插槽的对象 -
scopedSlots
: 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。 -
data
:传递给组件的整个数据对象,作为createElement
的第二个参数传入组件 -
parent
:对父组件的引用 -
listeners
: 一个包含了所有父组件为当前组件注册的事件监听器的对象 -
injections
: 如果使用了inject
选项,则该对象包含了应当被注入的属性。
看起来雨里雾里,其实没那么高深!
我们来个例子更直观,我们现在需要渲染一个列表,没有具体的交互,仅做展示使用,为了优化代码,我们决定使用函数式组件来渲染。
列表数据格式如下:
// list-data
[{
id : 1 ,
title : '学习Vue'
},{
id : 2 ,
title : '学习React'
},{
id : 3 ,
title : '学习Angular'
}]
如果我们通过模版方式来做的的话,我们可以定义如下:
// todoList.vue
<template functional>
<ul>
<li v-for="item in props.todoList">{{ item.title }}</li>
</ul>
</template>
如果我们通过 render
函数来创建的话(.js
文件,不是 .vue
文件), 那么应该是这样:
export default {
functional: true,
props: {
todoList: {
type: Array,
default: () => []
}
},
render: function (createElement, context) {
let oLi = context.props.todoList.map(item => {
return createElement('li', {
domProps: {
innerHTML: item.title
}
})
})
return createElement('ul', {}, oLi)
}
}
我们通过 import
在父组件导入一下,然后看看结果
效果一样,虽然 template
方式看起来简单的多,但是很多时候我还是比较倾向于 render
方式,因为它在修改或者条件判断的时候会比较方便,也省去了不少 v-if
,v-show
这些指令,看起来更加优雅。
自动化导入component
如果你定义了一系列的牛X的公共组件,然而你每次需要频繁的去 import xxx from '../xxx'
,还要
components : {
ComponentA
ComponentB
...
}
下面有种一劳永逸的方法,让你解放双手~
需要借助 webpack
里面的 require.context
方法,简单了解一下,通过它获取一个特定的上下文,然后从中读取指定目录下的文件和文件内容。
该方法有三个参数 directory
,useSubdirectories
和useSubdirectories
-
directory {String}
-读取文件的路径 -
useSubdirectories {Boolean}
-是否遍历文件的子目录 -
regExp {RegExp}
-匹配文件的正则
如果我要遍历 components
目录下的所有公共组件,我就可以这么做:
首先在components
目录下创建一个 baseComponent.js
文件,
// baseComponent.js
import Vue from 'vue'
const autoRequireComponent = require.context('./', false, /.vue$/)
autoRequireComponent.keys().forEach(file => {
//获取组件配置信息
const componentConfig = autoRequireComponent(file).default
//获取组件的名称 , 将 ./UButton.vue 名称替换成 UButton
const componentName = file.replace(/^\.\//, '').replace(/\.\w+$/, '')
//注册组件
Vue.component(componentName, componentConfig)
})
其中 autoRequireComponent.keys()
返回的就是一个文件名称的数组,类似于 ['UButton','UInput']
,然后遍历并通过 Vue.component
方法注册到全局
最后在 main.js
中 import
一下就搞定了,现在你可以在任意组件中调用你的公共组件了!
该方法在批量处理一些文件的时候会有奇效!
以上就是我日常开发中遇到或者总结到的一些东西,如果你有更好更优雅的方式,记得分享哈~