vue组件


组件的三大核心

自定义属性props

1、给props验证
教程:官方教程
我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。
要指定验证规则,需要用对象的形式来定义 prop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
props:{
test1:String,
test2: [String, Number],
test3: {
type: Number,
default: 100 //有默认值
},
test4: {
type: Object,
default: function () {
return { message: 'hello' } //数组/对象的默认值应当由一个工厂函数返回
}
},
test5: {
validator: function (value) {
return value > 10
} // 自定义验证函数,表示必须要大于10的值
},
test6: {
type: Number,
default: 13,
validator: function (value) {
return value > 10
} // 自定义验证函数,表示必须要大于10的值,使用场景:自作一个组件,当别人用你的组件的时候,小于10会进行报错
},
test7: {
type: Number,
required: true // 说明是必填项
}
},
//type:String Number Boolean Function Object Array Symbol

2、当父组件传入了props时,但是子组件没有声明有什么影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// parent.vue
<template>
<div class="parent">
<child
:go="'go'"
:foo="'foo'"
/>
</div>
</template>

// child.vue
<template>
<div class="child">
我是child
</div>
</template>

// 因为child没有props声明属性,go,foo会自动挂载达到子组件根元素上,渲染后就是这样:
<div go='go' foo='foo'>我是child</div>

// 怎么不让挂载
export default {
inheritAttrs: false
}

3、不改变父组件

1
2
3
4
5
6
7
8
9
10
11
12
// child.vue

export default {
props: {
type: String
},
computed: {
normalizedType: function () {
return this.type.toUpperCase();
}
}
}

4、子组件修改父组件值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//父组件
<btnModule @increa1='fn'></btnModule>
methods:{
fn(){
this.total += 1
}
}
//子组件btnModule
<button v-on:click="fn2"></button>
methods:{
fn2(){
this.$emit('increa1',100)
}
}

5、子组件修改父组件值(缩写版)
Vue 1.x 中的 .sync 修饰符,当一个子组件改变了一个带 .sync 的 prop 的值时,这个变化也会同步到父组件中所绑定的值,但破坏了单向数据流。从 2.3.0 起我们重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。

1
2
3
4
5
6
//父组件
<comp :foo.sync="bar"></comp>
//相当于<comp :foo="bar" @update:foo="val => bar = val"></comp>
//子组件
this.$emit('update:foo', newValue)
//子组件需要用到父组件值,还是需要prop,比如:props:['foo']

事件

1、事件驱动和数据驱动

事件驱动:查找需要操作的节点 -> 给节点添加相应的事件监听 -> 用户执行某事件 -> 调用 JavaScript 来修改节点。缺点:找节点和修改节点效率本身就很低。
数据驱动:Vue的一个核心思想。是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据,其流程如下:用户执行某个操作 -> 反馈到 VM 处理(可以导致 Model 变动) -> VM 层改变,通过绑定关系直接更新页面对应位置的数据。

2、修饰符事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 绑定原生事件
// 怎样给这个自定义组件 custom-component 绑定一个原生的 click 事件?
<custom-component @click="xxx"> // 这里的 @click 是自定义事件 click,并不是原生事件 click。只能在组件中使用,不能用在原生标签上。

<custom-component @click.native="xxx">组件内容</custom-component>

// v-model 在每次 input 事件触发后将输入框的值与数据进行同步
<input v-model.lazy="msg" /> // 转变为使用 change事件进行同步。适用于输入完所有内容后,光标离开才更新视图的场景。

// 过滤用户首尾空白字符
<input v-model.trim="msg">

// 过滤非数值
<input v-model.number="value" type="text" />

// 阻止事件冒泡
<a v-on:click.stop="doThis"></a>

// 阻止事件默认事件
<form v-on:submit.prevent="onSubmit"></form>

插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// parent.vue
<template>
<child>
<div v-slot:title>我是标题</div> // 2.6新语法,2.5以下用<div slot="title">我是标题</div>
<div>我是默认插槽</div>
</child>
</template>

// child.vue
<template>
<div>
<slot name="title"></slot>
<slot></slot>
</div>
</template>

组件之间通信

props/$emit

父组件通过props给子组件传值,子组件通过$emit给父组件传值

Vuex

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

Vuex与localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态

$attrs/$listeners

$attrs: 用于子组件/孙组件拿到父组件传入的props属性,并且子组件没有声明过的prop属性。(使用场景:将父组件的值传入孙组件中,子组件就可以省略写重复的props)
$listeners: 用于子组件/孙组件拿到父组件传入的非原生事件。(使用场景:孙组件要调用祖组件的方法,子组件可以省略写重复的方法名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// parent.vue
<template>
<div class="parent">
<child
data="'我是tip'"
title="'我是标题'"
:go="'go'"
:foo="'foo'"
@handleGo="handleGo"
@handleClick="handleClick"
/>
</div>
</template>

export default {
methods: {
handleGo (val) {
console.log(val)
},
handleClick (val) {
console.log(val)
}
}
}

// child.vue
<template>
<div class="child" @click="show">
我是child
<child2 v-bind="$attrs" v-on="$listenters" /> // 将props和非原生事件传入孙组件
</div>
</template>

export default {
props: {
data: String,
foo: String
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
methods: {
show() {
console.log(this.$attrs) // { title: '我是标题', go: 'go' },排除掉声明过的props
}
}
}

// child2.vue
<template>
<div class="child2" @click="show">
<button @click="handleGo">点我</button>
我是child2
</div>
</template>

export default {
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
methods: {
show() {
console.log(this.$attrs) // { title: '我是标题', go: 'go' },排除掉声明过的props
},
handleGo () {
this.$emit('handleGo', 'sdf') // 这里触发的是祖组件的函数
}
}
}

provide/inject

以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
弊端:子组件不能通信父组件,不能兄弟级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// parent.vue
<template>
<div class="parent" @click="hadleClick1">
我是parent
<child />
</div>
</template>

export default {
data () {
return {
t1: { a: 1 },
a1: { t: 2 }
}
},
provide () {
return {
t2: this.t1,
a2: this.a1,
fn: this.fn
}
},
methods: {
handleClick1 () {
this.$set(this.t1, 'a', 234)
console.log(this.t1) // { a: 234 }
},
fn (val) {
console.log(val)
}
}
}

// child.vue
<template>
<div class="child" @click="handleClick2">
我是child
</div>
</template>

export default {
inject: {
t3: 't2', // 简写版
a3: {
from: 'a2',
default: '324' // 没有的时候传默认值
}
fn: 'fn'
},
methods: {
handleClick2 () {
console.log(this.t3) // 第一次为{ a: 1, }, 第二次为{ a: 234 }
console.log(this.fn) // 传入的函数
}
}
}
$parent/$children与ref

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
弊端:不能跨级和兄弟级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 父组件
<template>
<child ref="comA"></child>
</template>
<script>
data () {
return { b: 2 }
},
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>

// child 子组件
<template>
<div @click="handleClick"></div>
</template>
export default {
data () {
return { title: 'Vue.js' }
},
methods: {
sayHello () {
window.alert('Hello');
},
handleClick () {
console.log(this.$parent.b)
}
}
}