前言:
Vue3.x版本已经在路上,在到来之前总结Vue2.x版本开发经验,趁机打好Vue2.x版本基础,本文列举以下开发技巧:
导入组件
原始写法:1
2
3
4
5
6import ChapterTitle from '@/components/list/ChapterTitleCom'
import CourseList from '@/components/list/CourseListCom'
export default {
// ...略
components: { ChapterTitle, CourseList }
}
避免写重复代码,可以参考webpack官网使用require.context写法;无论有多少文件,都能引入使用。
官网例子1
require.context('./test', false, /\.js$/)
require.context函数接受三个参数
- 读取文件路径
- 是否遍历文件子目录
- 匹配文件的正则
结合前端工程化,可以应用在Vue引入组件情景1
2
3
4
5
6
7
8const path = require('path')
const files = require.context('@/components/list', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
const path = path.basename(key, '.vue')
modules[path] = files(key).default || files(key)
})
components: modules
生命周期
beforeCreate 创建之前,在实例化之后,数据观测与事件配置之前被调用。
created 创建之后,在实例化完成后被调用。实例化已经完成相关配置,如:数据观测属性和方法运算,事件回调。此时$el属性不可见,挂载阶段没开始,一般在该周期发起ajax请求。
beforeMounted 挂载前,准备挂载阶段。模版在内存中编译完成,没有真正渲染到DOM页面,页面看不到真实数据。
mounted 挂载成功。$el被创建的vm.$el替换,此时页面已经真正渲染好,可以看到真实数据。
beforeUpdate 数据更新时调用。拿到最新data,在内存中重新渲染一颗新的DOM树,还没有挂载到页面,此时页面呈现的是旧数据。
updated 页面DOM已更新完毕,data中数据是最新的,页面呈现数据是最新data。
beforeDestory 实例销毁之前调用,此时组件还没销毁,能正常使用。如data,methods还能访问;该周期在服务端渲染期间不被调用。
destory 实例销毁之后,组件已完成销毁。如:此时data,methods都不可用。
data
可以存放各种数据类型;在数据变化时,相关视图用到的数据同步更新;data称为动态数据,任何情况下,都可以修改数据类型和数据结构。
代码片段1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<script>
export default() {
data() {
return {
str: 'vue',
num: 6,
bol: true,
obj: {
name: 'js'
},
fn: function() {}
}
}
}
</scritp>
watch
Vue提供了侦听属性watch,使用通用方式观察和响应Vue实例数据变化,watch可以执行ajax异步获取数据,操作DOM等任何逻辑。
用法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<div>{{ a }}</div>
<button @click="() => (a += 1)">click me</button>
</template>
<script>
export default {
data() {
return {
a: 1,
obj: {
b: 2
}
}
},
watch: {
a: function(oldVal, newVal) {
console.log(oldVal, newVal)
},
'obj.b': function(oldVal, newVal) {
console.log(oldVal, newVal)
}
}
}
</script>
实际遇到场景:组件创建时候初始化加载查询接口;监听input框,当input值发生变化时候重新加载筛选后的列表1
2
3
4
5
6
7
8
9
10
11
12<script>
export default {
created: {
this.getCourseList()
},
watch: {
inptVal: {
this.getCourseList()
}
}
}
</script>
使用watch提供的immediate与handler简写1
2
3
4
5
6
7
8
9
10<script>
export default {
watch: {
inpt: {
handler: 'getCourseList',
immediate: true
}
}
}
</script>
声明immediate:true 表示创建组件时立马执行一次。
利用watch提供deep属性,深度监听复杂数据类型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<div>{{ obj.a}}</div>
<button @click="() => (obj.a += 1)">click me</button>
</template>
<script>
export default {
data() {
return {
obj: {
a: 1
}
}
},
watch: {
obj: {
handler: function(oldVal, newVal) {
console.log(oldVal, newVal)
},
deep: true,
immediate: true
}
}
}
</script>
如果修改对象obj中a属性的值,不添加deep:true,只能监听对象obj的改变,不会触发回调。
computed
计算属性自动监听依赖值的变化,在监听值的变化时,触发一个回调;它有以下几点:
- 数据是响应式
- 可以进行数据逻辑处理,减少模版中计算逻辑
- 可以对计算属性中的数据监听
computed由两部分组成get和set,默认只有get,set需要手动添加,set不是直接修改计算属性,是修改它的依赖。1
2
3
4
5
6
7
8
9
10
11
12
13
14<script>
export default {
fullName: {
get: function() {
return this.firstName + '--' + this.lastName
},
set:function(newVal) {
// this.firstName = newVal; // 抛出error
var name = newVal.split('--');
this.firstName = name[0]; // 对它的依赖赋值
}
}
}
</script>
计算属性与普通属性区别:
计算属性可以像普通属性一样在模版中绑定计算属性,并且计算属性值是一个函数1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
computed: {
msg: function() {
return 'vue';
}
}
}
</script>
计算属性与方法区别:
computed可以缓存,只要相关依赖值没有发生变化,计算属性得到的值是之前缓存计算结果,不会多次执行;从上述代码发现必须要有return返回值;methods不能缓存。
除此之外,computed不能执行异步任务,计算属性必须是同步执行;watch同步与异步都可以操作,并且没有return。可以总结为computed能做的,watch都能做,反之不可以。
组件通信
组件实例作用域相互独立,不同组件之间的数据无法相互引用,针对不同场景使用不同组件传值方式。
- 父组件向子组件传值
父组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<div id="app">
<List :list="list"></List>
</div>
</template>
<script>
import List from '@components/List'
export default {
name: 'App',
data() {
return {
list: ['小社区', '社区', '大社区']
}
},
components: {
List
}
}
</script>
子组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<ul>
<li v-for="(item, index)in list" :key="index">{{ item }}</li>
</ul>
</template>
<script>
export default {
name: 'List',
props: {
list: {
type: Array,
required: true
}
}
}
</script>
在子组件接收props支持数组和对象形式,推荐使用对象形式更严谨。其中type值可为:string/number/boolean/function/object/Symbol;
required:true 表示是否必传。
props在数据传递时不能修改它的数据类型,而且在子组件不允许直接操作传递过来的props,可通过其它方式简介修改,如:使用computed计算属性;也可以在组件data中重新定义。
父组件向子组件传递数据,组件中的数据有三种形式:data/props/computed。
子组件向父组件传值
父组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div id="app">
<List :list="list" @updateName="getName"></List>
</div>
</template>
<script>
import List from '@components/List'
export default {
methdds: {
getName: function(name) {
console.log(name)
}
},
components: {
List
}
}
</script>
子组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<ul>
<li v-for="(item, index)"
:key="index"
@click="getMyName(item)">{{ item }}</li>
<ul>
</template>
<script>
export default{
methods: {
getMyName: function(name) {
this.$emit('updateName', name);
}
}
}
</script>
$emit与$on
声明一个全局Vue实例变量把所有通信数据,事件监听都存储到这个变量,实现任何组件间的通信,包括跨级、父子、兄弟;这种方式推荐小项目。
实现原理:使用$emit和$on并实例化一个全局Vue实现数据共享。
main.js1
export default Bus = new Vue()
组件B1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<button
@click="sendC">
click me
</button>
</template>
<script>
import Bus from 'main'
export default {
methods: {
sendC: function() {
Bus.$emit('msg', '来自组件B');
}
}
}
</script>
组件C1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<script>
import Bus from 'main'
export default{
mounted: {
this.getVal();
},
methods: {
getVal: function() {
Bus.$on('msg', msg=> {
console.log(msg);
})
}
}
}
</script>
$attrs
包含父作用域中不被prop所识别的特征绑定(class和style除外)。当一个组件没有声明任何prop时,这里包含所有父作用域的绑定(class和style除外),并且可以通过v-bind=”$attrs”传入组件内部。
父组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<div>
<Child
name="name"
age="age"
height="height"
title="web">
</Child>
</div>
</template>
<script>
import Child from '@/components/Child'
export default {
data() {
return {
name: 'vue',
age: 6,
height: 180
}
}
}
</script>
子组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
inheritAttrs: false,
props: {
height: String,
default: ''
},
mounted() {
console.log(this.$attrs);
// { name: "vue", age: 6, title: 'web' }
}
}
</script>
inheritAttrs默认值为true,设置false就隐藏,关闭自动挂载到组件根元素上的没有在props声明的属性。$attrs就是剔除被props定义的属性。
$listeners
包含父作用域中(不含.navtive修饰符)v-on事件监听器,
父组件方法可以通过v-on=”$listeners”传入内部组件。在创建更高层次的组件时非常有用。
场景:子组件需要调用父组件的方法
父组件1
<Parent @change="change" />
子组件1
2
3
4
5
6
7<script>
export default {
mounted: {
console.log(this.$listeners) // 拿到 change事件
}
}
</script>
provide与inject
这对选项需要一起使用,主要为高阶插件和组件库提供。不推荐直接应用在程序中。以允许一个祖先组件向其所有子孙后代注入一个依赖,不论层次有多深,并在起上下有关系成立的时间里始终生效。
父组件1
2
3
4
5
6
7
8
9
10<script>
export default {
provide: { // provide是一个对象
bar: '这是父',
fn: () => {
console.log('函数被调用')
}
}
}
</script>
子或者孙子组件:1
2
3
4
5
6
7
8
9<script>
export default {
inject: ['bar', 'fn'] // 对象 数组注入到子或孙子组件
mounted() {
console.log(this.bar);
this.fn();
}
}
</script>
pronide与inject绑定并不是可响应的。这是官方刻意为之的。如果传入一个可监听对象,那么其对象的属性还是可响应的。
父组件:1
2
3
4
5
6
7
8
9
10<script>
export default {
provide: {
bar: '这是父'
},
mounted() {
this.bar = '这是子';
}
}
</script>
子或孙子组件:1
2
3
4
5
6
7
8<script>
export default {
inject: ['bar'],
mounted() {
console.log(this.bar); // 这是父
}
}
</script>
$parent
父组件:1
2
3
4
5
6
7
8<script>
export default{
mounted() {
console.log(this.$children)
// 能拿到一级子组件的属性和方法
}
}
</script>
$children
子组件1
2
3
4
5
6
7
8<script>
export default{
mounted() {
console.log(this.parent)
// 可以拿到一级父组件的属性和方法
}
}
</script>
$parent和$children并不保证顺序,又不是响应式的。
$refs
ref用在普通DOM元素上,指向就是DOM元素;用在子组件,指向组件实例
父组件1
2
3
4
5
6
7
8
9
10
11
12<template>
<Child ref="child" />
</template>
<script>
import Child from '@/components/Child'
export default {
mounted() {
const child = this.$refs.child;
console.log(child.name) // vue
}
}
</script>
子组件1
2
3
4
5
6
7
8
9<script>
export default{
data() {
return {
name: 'vue'
}
}
}
</script>
$root
1 | <script> |
修饰符
- .sync
父组件:
1 | <Parent :msg.sync="title" /> |
子组件:1
2
3
4
5
6
7<script>
export default {
mounted() {
this.$emit('update:msg', '新的msg')
}
}
</script>
自定义事件添加原生click事件,使用native修饰符
1 | <template> |
- .lazy
默认情况下,v-model在每一次input事件触发后将输入的值与数据同步。可以使用lazy修饰符,转变使用change事件同步,输入完内容后,光标离开才更新视图。
1 | <template> |
- .number
输入的值转化为数值类型。如果先输入的是数字,会限制输入的只能是数字,视图上只显示数字;如果先输入的是字母,相当于没有添加number修饰符,输入什么视图显示什么。
1 | <template> |
- .trim
过滤input输入内容前后空格,中间空格不过滤。
1 | <template> |
- .stop
一键阻止事件冒泡,相当于event.stopPropagetion()
1 | <template> |
- .prevent
阻止事件的默认行为,相当于event.preventDefault()
1 | <template> |
@click.prevent.self会阻止所有点击;@click.self.prevent只会阻止元素自身的点击。
- .self
当事件是从事件绑定的元素本身触发回调。
1 | <template> |
- .once
只能用一次,数据发生变化也不会改变;绑定事件以后只能触发一次
1 | <template> |
.capture
完整的事件机制:捕获阶段->目标阶段->冒泡节点。添加capture就反过来,事件触发从包含这个元素顶层开始往下传播。config.keyCodes自定义按键修饰符别名
1 | <script> |
v-slot
简称插槽,作用是将父组件的template传入子组件。插槽有匿名插槽和作用域插槽。
匿名插槽
父组件:1
2
3
4
5
6<Parent>
<template v-slot:default>
任意内容
<p>这里是匿名插槽</p>
</template>
</Parent>
子组件:1
<slot>插槽默认值</slot>
具名插槽,相对匿名插槽组件slot带有name名称
父组件:1
2
3
4
5
6<Parent>
<template v-slot:child>
任意内容
<p>这里是具名插槽</p>
</template>
</Parent>
子组件:1
<slot name="child">这是子插槽</slot>
- 作用域插槽
子组件中的数据可以被父组件拿到
父组件:
1 | <Parent> |
slotData可以是任意名称,接收是子组件标签slot上属性数据
子组件:1
2
3
4
5
6
7
8
9
10
11
12<slot name="child" :msg="msg">
{{ msg }}
<slot>
<script>
export default {
data() {
return {
msg: '插槽'
}
}
}
<script>
msg是默认数据,在父组件没有v-slot:child=”slotData”时填充。
filters过滤器
可用于常见的文本数据进行格式化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<template>
<div>{{ msg| upperCase(true) }}</div>
</template>
<script>
export default {
filters: {
upperCase: function(val, bol) {
const str = val.toString();
if(!!bol) {
return val.charAt(0).toUpperCase() + val.slice(1)
}else{
return str.toUpperCase()
}
}
}
}
</script>
v-if与v-show
v-if 根据条件为false时,不存在DOM;在切换过程中,需要经过销毁和重建,开销高。
v-show 一直存在DOM中,使用CSS的display属性控制,开销小。
Vue.set
额外添加数据有可能不是响应式数据,使用set加入Vue响应式中。1
Vue.set(目标对象, 目标对象属性, 目标对象属性值)
keep-alive
用来对组件进行缓存,节省性能。由于是一个抽象组件,在页面渲染完毕后不会被渲染成一个DOM元素。1
2
3<keep-alive>
<Child></Child>
</keep-alive>
在组件切换时,activated/deactivated钩子函数会被执行,被包裹在kepp-alive中的组件状态,会被保留。