vue_组件

组件化应用构建

基础示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这里有一个 Vue 组件的示例:

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

new Vue({ el: '#components-demo' })
实例化要放后面


HTML
<div id="components-demo">
<button-counter></button-counter>
</div>

创建组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('todo-item', {
template: '<li>123</li>'
})

var app6 = new Vue({
el:'#app6',
data:{
i
}
});

HTML
<ol id="app6">
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item></todo-item>
</ol>

组件的复用

1
2
3
4
5
6
7
8
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>


注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。

组件传值

父组件子组件互相传值

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
html
<div id="demo">
<input type="text" v-model="valuedata">
<button @click='sub_fn'>提交</button>
<ul>
<item-li v-for="(item,index) in lists" :key="index" :content="item" :index="index" @delete="handledelete"></item-li>
</ul>
</div>


js
<script>
Vue.component('item-li',{
props:['content','index'],
template:'<li @click="handleclick">{{content}}</li>',
methods:{
handleclick:function(){
this.$emit('delete',this.index)
}
}
})
new Vue({
el:'#demo',
data:{
valuedata:'',
lists:[]
},
methods:{
sub_fn:function(){
this.lists.push(this.valuedata)
},
handledelete:function(e){
this.lists.splice(e,1)

}
}
})
</script>

父向子组件传值

1.父组件parent代码如下:

1
2
3
4
5
6
7
8
template
<son psMsg="父传子的内容:叫爸爸"></son>
如果传对象
<sonpsMsg="mydata"></son>

script
import son from './Son' //引入子组件
components:{son},

2.子组件son代码如下:

1
2
3
4
5
template
<p>子组件接收到内容:{{ psMsg }}</p>

script
props:['psMsg'],//接手psMsg值

通过 Prop 向子组件传递数据

子组件向父组件传值

子组件 this.$emit(‘func’,this.msg)传出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="app">
<input @click="sendMsg" type="button" value="给父组件传递值">
</div>
</template>
<script>
export default {

data () {
return {
//将msg传递给父组件
msg: "我是子组件的msg",
}
},
methods:{
sendMsg(){
//func: 是父组件指定的传数据绑定的函数,this.msg:子组件给父组件传递的数据
this.$emit('func',this.msg)
}
}
}
</script>

父组件 @func=”getMsgFormSon” 接收并调用方法

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
<template>
<div class="app">
<child @func="getMsgFormSon"></child>
</div>
</template>

<script>
import child from './child.vue'
export default {
data () {
return {
msgFormSon: "this is msg"
}
},
components:{
child,
},
methods:{
getMsgFormSon(data){
this.msgFormSon = data
console.log(this.msgFormSon)
}
}
}
</script>

通过插槽分发内容

和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<alert-box>
Something bad happened.
</alert-box>
可能会渲染出这样的东西:

幸好,Vue 自定义的 <slot> 元素让这变得非常简单:

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

我们只要在需要的地方加入插槽就行了

动态组件

1
可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括

已注册组件的名字,或
一个组件的选项对象

计算属性
computed: {
currentTabComponent: function () {
return "tab-home";
}
}


<component ></component>
变成
<tab-home><tab-home/>
==
<component is="tab-home"></component>

深入了解组件

组件注册

组件名大小写

kebab-case
PascalCase

全局注册

Vue.component 注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('my-component-name', {
// ... 选项 ...
})
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>

全局注册例子

1
2
3
4
5
6
Vue.component('item-li',{
props:['content'],
template:'<li>{{content}}</li>'
})


vue-cli 使用

全局组件
main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hyyyTitle from '@/mycomponents/hyyyTitle.vue'

Vue.component('hyyyTitle',hyyyTitle)

export default {

name: 'Detail',

data(){

return {

}

},

components:{

hyyyTitle

}

}

局部注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// var ItemLi = {
// template:'<li>1</li>'
// }
new Vue({
el:'#demo',
data:{
valuedata:'',
lists:[]
},
// components:{
// ItemLi:ItemLi
// },
methods:{
sub_fn:function(){
this.lists.push(this.valuedata)
}
}
})

vue-cli 使用

1
2
3
4
5
6
7
8
9
10
11
12
// import hyyyTitle from '@/mycomponents/hyyyTitle.vue'

export default {
name: 'Detail',
data(){
return {
}
},
components:{
hyyyTitle
}
}

使用组件

vue文件使用标签

1
<hyyyTitle />

模块系统

在模块系统中局部注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果你还在阅读,说明你使用了诸如 Babel 和 webpack 的模块系统。在这些情况下,我们推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。

然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.jsComponentB.vue 文件中:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
components: {
ComponentA,
ComponentC
},
// ...
}
现在 ComponentAComponentC 都可以在 ComponentB 的模板中使用了。

基础组件的自动化全局注册

https://vuejs.bootcss.com/guide/components-registration.html#基础组件的自动化全局注册

导入基础组件库到全局

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
https://vuejs.bootcss.com/guide/components-registration.html#基础组件的自动化全局注册



如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)

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

// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。这里有一个真实项目情景下的示例。

Prop
Prop 的大小写 (camelCase vs kebab-case)

1
2
3
4
5
6
7
8
9
10
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
重申一次,如果你使用字符串模板,那么这个限制就不存在了。

Prop 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
https://vuejs.bootcss.com/guide/components-props.html#Prop-类型


到这里,我们只看到了以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。你会在这个页面接下来的部分看到类型检查和其它 prop 验证。

https://vuejs.bootcss.com/guide/components-props.html#Prop-验证

传递静态或动态 Prop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
https://vuejs.bootcss.com/guide/components-props.html#传递静态或动态-Prop

像这样,你已经知道了可以像这样给 prop 传入一个静态的值:

<blog-post title="My journey with Vue"></blog-post>
你也知道 prop 可以通过 v-bind 动态赋值,例如:

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。

传入一个数字
传入一个布尔值
传入一个数组
传入一个对象
传入一个对象的所有 property

单向数据流

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
https://vuejs.bootcss.com/guide/components-props.html#单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

这里有两种常见的试图变更一个 prop 的情形:

这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

Prop 验证

https://vuejs.bootcss.com/guide/components-props.html#Prop-验证

我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。

为了定制 prop 的验证方式,你可以为 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
25
26
27
28
29
30
31
32
33
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。

类型检查

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
https://vuejs.bootcss.com/guide/components-props.html#类型检查

type 可以是下列原生构造函数中的一个:

String
Number
Boolean
Array
Object
Date
Function
Symbol
额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
你可以使用:

Vue.component('blog-post', {
props: {
author: Person
}
})
来验证 author prop 的值是否是通过 new Person 创建的。

非 Prop 的 Attribute

https://vuejs.bootcss.com/guide/components-props.html#非-Prop-的-Attribute

替换/合并已有的 Attribute
禁用 Attribute 继承

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
https://vuejs.bootcss.com/guide/components-props.html#非-Prop-的-Attribute	

如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:

Vue.component('my-component', {
inheritAttrs: false,
// ...
})
这尤其适合配合实例的 $attrs property 使用,该 property 包含了传递给一个组件的 attribute 名和 attribute 值,例如:

{
required: true,
placeholder: 'Enter your username'
}
有了 inheritAttrs: false$attrs,你就可以手动决定这些 attribute 会被赋予哪个元素。在撰写基础组件的时候是常会用到的:

Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。

自定义事件

事件名
推荐你始终使用 kebab-case 的事件名。
自定义组件的 v-model

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
https://vuejs.bootcss.com/guide/components-props.html#非-Prop-的-Attribute


一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

注意你仍然需要在组件的 props 选项里声明 checked 这个 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
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
https://vuejs.bootcss.com/guide/components-props.html#非-Prop-的-Attribute

可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:


你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:

<base-input v-on:focus.native="onFocus"></base-input>
在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。比如上述 <base-input> 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:

<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。

为了解决这个问题,Vue 提供了一个 $listeners property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了这个 $listeners property,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

.native 修饰符
Vue 提供了一个 $listeners property
Object.assign()将所有对象整合成一个新对象
.sync 修饰符
对一个 prop 进行“双向绑定”。


vue_组件
http://www.dwyblog.cn/2020/06/05/vue学习笔记_组件/
作者
幸福来敲门
发布于
2020年6月5日
许可协议