深入理解 Vuejs 组件
本文主要归纳在 Vuejs 学习过程中对于 Vuejs 组件的各个相关要点。由于本人水平有限,如文中出现错误请多多包涵并指正,感谢。如果需要看更清晰的代码高亮,请跳转至我的个人站点的 深入理解 Vuejs 组件 查看本文。
组件使用细节
is属性
我们通常使用 is
属性解决模板标签 bug 的问题。下面我们通过一个 table
标签的 bug 案例进行说明。
我们先写一个简单的 Vue 实例,并创造一个 row
的组件,将它的模板 template
置为 '<tr><td>this is a row</td></tr>'
,按照下面的示例进行放置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>is属性</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</head>
<body>
<div id="app">
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
<script>
Vue.component('row',{
template: '<tr><td>this is a row</td></tr>'
})
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
该示例中,由于 H5 的规范 table
标签下 tbody
下只能是 tr
,所以浏览器在渲染的时候出了问题。可以看到组件row
渲染的 this is a row 都跑到了 table
之外。

解决这个问题的方法就是,我们按照规范在 tbody
之下使用 tr
。但我们用 is=
将 tr
变成 row
组件。
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
这样我们在遵循规范的同时,也使用了 Vuejs 的组件模板。可以看到接下来的浏览器 DOM 渲染已经正常。

在使用
ul
时,同样建议使用li
与is=
,而不是直接使用组件模板。
在使用select
时,同样建议使用option
与is=
,而不是直接使用组件模板。
子组件 data
我们还是通过上面这个已有的案例进行演示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</head>
<body>
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component('row',{
data: {
content: 'this is a row'
},
template: '<tr><td>{{content}}</td></tr>'
})
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
经过之前的修改,我们将 tr
标签的 bug 解决掉了,并进行了修正。我们想在 Vue.component
的子组件中,添加数据 data
,并在模板 template
中使用插值表达式使用该数据内容。但这种写法打开浏览器是没有任何显示的。
因为在子组件中定义 data
时,data
必须是一个函数 function
,return
值为一个对象。而不能直接是一个对象。因为子组件不像根组件,只会被调用一次,可能在不同的地方被调用多次。所以通过函数 function
来让每一个子组件都有独立的数据存储,就不会出现多个子组件相互影响的情况。
即在子组件中正确的写法应该是:
Vue.component('row',{
data: function(){
return {
content: 'this is a row'
}
},
template: '<tr><td>{{content}}</td></tr>'
})
ref 引用
在 Vuejs 中,使用 ref
引用的方式,可以找到相关的 DOM 元素。
在 div
标签中添加一个 ref="hello"
,标记这个标签的引用名为 hello
。并给他绑定一个事件,在点击它之后,输出出这个引用节点的 innerText
<body>
<div id="app">
<div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
var vm = new Vue({
el: "#app",
methods: {
handleClick: function(){
alert(this.$refs.hello.innerText)
}
}
})
</script>
</body>
而当在一个组件中去设置 ref
,然后通过 this.$refs.name
获取 ref
里面的内容时,这个时候获取到的内容是子组件内容的引用。
参考下面的计数器求和案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计数器求和</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<div>{{total}}</div>
</div>
<script>
Vue.component('counter',{
template: '<div @click="handleClick">{{number}}</div>',
data: function(){
return {
number: 0
}
},
methods: {
handleClick: function(){
this.number ++
this.$emit('change')
}
}
})
var vm = new Vue({
el: "#app",
data: {
total: 0
},
methods: {
handleChange: function(){
this.total = this.$refs.one.number + this.$refs.two.number
}
}
})
</script>
</body>
</html>
在子组件中,绑定了 handleClick
事件使其每次点击后自增1,并且发布 $emit
将 change
传递给父组件,在组件中监听 @change
,执行 handleChange
事件,在父组件 methods
中设置 handleChange
事件,并使用 this.$refs.one.number
来获取子组件内容的引用。
父子组件的数据传递
Vue 中的单向数据流:即子组件只能使用父组件传递过来的数据,不能修改这些数据。因为这些数据很可能在其他地方被其他组件进行使用。
所以当子组件在收到父组件传递过来的数据,并在后续可能要对这些数据进行修改时。可以先将 props
里面获取到的数据,在子组件自己的 data
的 return
中使用一个 number
进行复制,并在后续修改这个 number
即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父子组件传值</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ['count'],
//在 data 的 return 中复制一份 父组件传递过来的值
data: function(){
return {
number: this.count
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick: function(){
// 在子组件中不修改父组件传递过来的 count,而是修改自己 data 中的 number
this.number ++
}
}
}
var vm = new Vue({
el: "#app",
components: {
counter: counter
}
})
</script>
</body>
</html>
传值总结
- 父组件通过属性的形式向子组件进行传值
- 子组件通过事件触发的形式向父组件传值
- 父子组件传值时,有单向数据流的规定。父组件可以向子组件传递任何的数据,但子组件不能修改父组件传递过来的数据。如果一定要进行修改,只能通过修改复制副本的方式进行。
组件参数校验 和 非props特性
组件参数校验
当子组件接收父组件数据类型要进行参数校验时,是可以通过组件参数校验来定义规则的。例如限制子组件接收父组件传递数据的类型,此时props
后不再使用数组,而是使用对象。
- 当传递过来的值只接收数字类型时

- 当传递过来的值只接收字符串类型时

- 当传递过来的值既可以接收字符串类型、也可以接收数字类型时

当做了组件参数校验,在传递过程中如果传递了组件参数校验规定之外的类型,就会报错(这里我们传递的是一个 Object
)。

自定义校验器
props
中的 content
之后也可以写成对象形式,设置更多的参数。
Vue.component('child',{
props: {
content: {
type: String,
required: false, //设置是否必须传递
default: 'default value' //设置默认传递值 -- 无传递时传递的默认值
}
},
template: '<div>{{content}}</div>'
})
type
设置传递类型;required
设置是否必须传递,false
为非必须传递;default
设置默认传递值,在无传递时,传递该值。

当父组件调用子组件传递了 content
时,默认值便不会生效。

除此之外,还可以限制传递字符串的长度等等。借助 validator
Vue.component('child',{
props: {
content: {
type: String,
//对传入属性通过校验器要求它的长度必须大于5
validator: function(value){
return value.length > 5
}
}
},
template: '<div>{{content}}</div>'
})
在这个案例中,传入的属性长度必须超过 5 ,如果没有就会出现报错。

prop 特性 与 非 props 特性
prop 特性
props
特性: 当父组件使用子组件时,通过属性向子组件传值,恰好子组件也声明了对父组件传递过来的属性的接收。即当父组件调用子组件时,传递了 content
;子组件在 props
里面也声明了 content
。所以父子组件有一个对应的关系。这种形式的属性,就称之为 props
特性。
prop
特性特点:
- 属性的传递,不会在 DOM 标签进行显示
- 当父组件传递给子组件之后,子组件可以直接通过插值表达式或者通过
this.content
取得内容。
非 props 特性
非 props
特性:父组件向子组件传递了一个属性,子组件并没有 props
的内容,即子组件并没有声明要接收父组件传递过来的内容。

非 prop
特性特点:
- 无法获取父组件内容,因为没有声明
- 属性会展示在子组件最外层的 DOM 标签的 HTML 属性里。

原生事件
在下面示例中,代码这么书写在点击 Child
的时候,事件是不会被触发的。因为这个 @click
事件实际上是绑定的一个自定义的事件。但真正的鼠标点击 click
事件并不是绑定的这个事件。
<body>
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div>Child</div>'
})
var vm = new Vue({
el: "#app",
methods: {
handleClick: function(){
alert('click')
}
}
})
</script>
</body>
如果我们想要触发这个自定义的 click
事件,应该把 @click
写到子组件的 template
中的 div
元素上。我们将代码改写成下面的样子,点击 Chlid
弹出 chlidClick
。因为在 div
元素上绑定的事件是原生的事件,而之前在 child
上绑定的事件是监听的一个自定义事件。
<body>
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>',
methods: {
handleChildClick: function(){
alert('chlidClick')
}
}
})
var vm = new Vue({
el: "#app",
methods: {
handleClick: function(){
alert('click')
}
}
})
</script>
</body>
而自定义事件,只有通过 this.$emit
去触发。
<body>
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>',
methods: {
handleChildClick: function(){
alert('chlidClick')
this.$emit('click')
}
}
})
var vm = new Vue({
el: "#app",
methods: {
handleClick: function(){
alert('click')
}
}
})
</script>
</body>
组件监听内部原生事件
通常使用在 @click
之后加上 .native
修饰符达到直接在组件上监听原生事件的效果。
<body>
<div id="app">
<child @click.native="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div>Child</div>'
})
var vm = new Vue({
el: "#app",
methods: {
handleClick: function(){
alert('click')
}
}
})
</script>
</body>
非父子组件间传值
将左侧的网页用右侧的图进行表示。即细分组件之后,再进行二次细分。

当出现以下情况,第二层的一个组件要跟第一层的大组件进行通信。这个时候就是父子组件的传值。即父组件通过 props
向子组件传值,子组件通过事件触发向父组件传值。

当第三层的组件要和第一层的大组件进行通信。甚至两个不同二层组件下的三层组件要进行通信时。应该采用什么方法呢?
这时显然就不能使用逐层传递的方式了。因为这样的操作会使得代码非常的复杂。

既然不是父子组件之间传值,说明这两个组件之间不存在父子关系。如之前提到的第三层的组件要向第一层的大组件进行传值,两个不同二层组件下的三层组件要进行传值。这些都是非父子组件传值。
解决方案
一般有两种方式来解决 Vue 里面复杂的非父子组件之间传值的问题。
- 一种是借助 Vue 官方提供的一种数据层的框架 Vuex。
- 另一种是使用 发布 / 订阅 模式来解决非父子组件之间传值的问题,也被称之为 总线机制。
下面着重讲解如何使用 总线机制 解决非父子组件之间传值的问题。
Bus / 总线 / 发布订阅模式 / 观察者模式
通过一个案例来实现该模式,当点击 even
时,yao
变为 even
。当点击 yao
时,even
变为 yao
。
首先 new
一个 Vue 的实例,将其赋值给 Vue.prototype.bus
。即给 Vue.prototype
上挂载了一个名为 bus
的属性。这个属性,指向 Vue 的实例。只要在之后,调用 new Vue()
或者创建组件的时候,每一个组件上都会拥有 bus
这个属性。都指向同一个 Vue 的实例。
通过 this.bus.$emit
向外触发事件,再借助生命周期钩子 mounted
通过 this.bus.$on
监听事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>非父子组件间传值 Bus/总线/发布订阅模式/观察者模式)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<child content="even"></child>
<child content="yao"></child>
</div>
<script>
// new 一个 Vue 的实例,将其赋值给 Vue.prototype.bus
Vue.prototype.bus = new Vue()
Vue.component('child',{
data: function(){
return {
//因为不能改变传递过来的值 所以复制一份
selfContent: this.content
}
},
props: {
content: String
},
template: '<div @click="handleClick">{{selfContent}}</div>',
methods: {
handleClick: function(){
//实例上挂载的bus,通过 $emit 方法向外触发事件
this.bus.$emit('change',this.selfContent)
}
},
//借助生命周期钩子 通过 $on 方法 监听 change 事件
mounted: function(){
var _this = this
this.bus.$on('change',function(msg){
_this.selfContent = msg
})
}
})
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
插槽 slot
子组件除了展示 p
标签中的 hello 之外,还需要展示一块内容,而这部分内容不是子组件决定的,而是父组件传递过来的。如果使用 v-html
搭配 content
属性传递值,会出现外部必须包裹 div
的问题。这个场景就应该使用插槽 slot
。
在父组件使用 child
的时候,在标签内部,使用 h1
标签,并写入 yao 。这样就可以了。
<div id="app">
<child>
<h1>yao</h1>
</child>
</div>
在子组件的模板里,使用 <slot></slot>
就可以使之前写入的 yao 显示出来了。
Vue.component('child',{
template: '<div><p>hello</p><slot></slot></div>'
})
var vm = new Vue({
el: "#app"
})
除此之外, <slot></slot>
之前还可以添加默认内容,即 <slot>默认内容</slot>
。添加默认内容的时候,如果在父组件使用子组件时,不传递插槽内容的话,就会显示默认内容,如果父组件使用子组件时,传递了插槽内容的话,就会显示传递的插槽内容,而不会显示默认内容。
具名插槽
当我有多个 slot
插槽需要进行填充的时候,可以使用具名插槽,即给插槽命名。例如下列示例中的 header
和 footer
都是由外部传递的情况。
在父组件使用子组件的过程中,给插槽添加 slot=""
属性,对应之后的插槽命名。
<div id="app">
<body-content>
<header slot="header">header</header>
<footer slot="footer">footer</footer>
</body-content>
</div>
在 slot
中使用 name=""
给插槽命名。
Vue.component('body-content',{
template: `<div>
<slot name="header"></slot>
<div class="content">content</div>
<slot name="footer"></slot>
</div>`
})
var vm = new Vue({
el: "#app"
})
具名插槽同样可以拥有默认值。
作用域插槽
当子组件做循环,或者某一部分的 DOM 结构是由外部传递进来时,使用作用域插槽。
作用域插槽必须是 template
开头和结尾的内容,同时这个插槽声明从子组件接收的数据都放在 props
里面,然后通过相应的模板对子组件进行展示。
<div id="app">
<child>
<template slot-scope="props">
<li>{{props.item}} - hello</li>
</template>
</child>
</div>
Vue.component('child',{
data: function(){
return {
list: [1,2,3,4]
}
},
template: `<div>
<ul>
<slot v-for="item of list"
:item=item
></slot>
</ul>
</div>`
})
var vm = new Vue({
el: "#app"
})
动态组件 与 v-once 指令
下面代码可以实现点击 button
按钮的切换效果,除了这种方式之外,还可以使用动态组件的方式实现。
<body>
<div id="app">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one',{
template: '<div>child-one</div>'
})
Vue.component('child-two',{
template: '<div>child-two</div>'
})
var vm = new Vue({
el: "#app",
data: {
type: 'child-one'
},
methods: {
handleBtnClick: function(){
this.type = this.type === 'child-one' ? 'child-two' : 'child-one'
}
}
})
</script>
</body>
动态组件
使用 component
标签,并使用 :is
绑定数据。即可以实现上面示例中相同的效果。
即根据 :is
对应值的变化,自动的加载不同的组件。
<div id="app">
<component :is="type"></component>
<!-- <child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two> -->
<button @click="handleBtnClick">change</button>
</div>
v-once 指令
在 Vue 中通过 v-once
指令可以提高静态内容的展示效率。例如上面的示例中,当我们不使用动态组件而使用下面的方式进行组件调用的时候。每次点击 button
,都会摧毁当前组件,然后创建一个新的组件。
<div id="app">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
Vue.component('child-one',{
template: '<div>child-one</div>'
})
Vue.component('child-two',{
template: '<div>child-two</div>'
})
如果我们在这两个组件模板中加上 v-once
指令。在 child-one
和 child-two
第一次渲染的时候,就会被放入内存之中。当进行切换的时候,就并不需要重新创建一个组件了,而是从内存中去拿出以前的组件,所以性能更高。
Vue.component('child-one',{
template: '<div v-once>child-one</div>'
})
Vue.component('child-two',{
template: '<div v-once>child-two</div>'
})
深入理解 Vuejs 组件的更多相关文章
- 深入理解 Vuejs 动画效果
本文主要归纳在 Vuejs 学习过程中对于 Vuejs 动画效果的各个相关要点.由于本人水平有限,如文中出现错误请多多包涵并指正,感谢.如果需要看更清晰的代码高亮,请跳转至我的个人站点的 深入理解 V ...
- 深入理解 Vue 组件
深入理解 Vue 组件 组件使用中的细节点 使用 is 属性,解决组件使用中的bug问题 <!DOCTYPE html> <html lang="en"> ...
- 不一样的角度理解Vue组件
什么是组件 以Java.C#等面向对象编程语言的角度去理解Vue组件,能够发现组件和面向对象编程的方式和风格很相似.一切事物皆为对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽 ...
- 尝试用面向对象思维理解Vue组件
什么是组件 用面向对象的思维去理解Vue组件,可以将所有的事物都抽象为对象,而类或者说是组件,都具有属性和操作. 如抽取人类为组件,其基本的属性有姓名.年龄.国籍:基本的方法有吃饭.睡觉.跑步等. & ...
- VueJs组件prop验证简单理解
今天看了vuejs的组件,看到了prop组件,主要作用是在传入数据的时候对传入的值做判断,写了个小例子. <div id="app"> <my-child :nu ...
- Vuejs - 组件式开发
初识组件 组件(Component)绝对是 Vue 最强大的功能之一.它可以扩展HTML元素,封装可复用代码.从较高层面讲,可以理解组件为自定义的HTML元素,Vue 的编译器为它添加了特殊强大的功能 ...
- 给定制的vuejs组件添加v-model双向绑定支持
用过vuejs的前端工程师,对于v-model一定印象深刻.它向类似textarea,input等原生html原生添加双向数据绑定的能力非常方便.但是对于你的定制vue组件并不是能够直接应用v-mod ...
- 如何理解Unity组件化开发模式
Unity的开发模式核心:节点和组件,组件可以加载到任何节点上,每个组件都有 gameobject 属性,可以通过这个属性获取到该节点,即游戏物体. 也就是说游戏物体由节点和组件构成,每个组件表示物体 ...
- 理解React组件的生命周期
本文作者写作的时间较早,所以里面会出现很多的旧版ES5的时代的方法.不过,虽然如此并不影响读者理解组件的生命周期.反而是作者分为几种不同的触发机制来解释生命周期的各个方法,让读者更加容易理解涉及到的概 ...
随机推荐
- 使用OrgChart插件生成家谱组织结构图
1.orgchart插件: github地址:https://github.com/dabeng/OrgChart 2.前端代码: //1.加载树形数据:ajax请求获取json格式的数据(flag参 ...
- Process.waitFor()导致主线程堵塞问题
今日开发的时候使用jdk自带的运行时变量 RunTime.getRunTime() 去执行bash命令.因为该bash操作耗时比较长,所以使用了Process.waitFor()去等待子线程运行结束. ...
- 大数据框架-Mapreduce过程
1.Shuffle [从mapTask到reduceTask: Mapper -> Partitioner ->Combiner -> Sort ->Reducer] mapp ...
- String的非空判断、Integer的非空判断、list的大小判断,对象的非空判断
1.String的非空判断. StringUtils.isNotEmpty(String str); 2.Integer的非空判断. null != Integer ; 3.list的大小判断. li ...
- Reading Notes : 180215 计算机系统
读书<计算机组成原理>,<鸟哥的Linux私房菜 基础篇>百度百科,内容摘自<计算机组成原理>,<鸟哥的Linux私房菜 基础篇> 计算机系统 在前面几 ...
- Swift_TableView(delegate,dataSource,prefetchDataSource 详解)
Swift_TableView(delegate,dataSource,prefetchDataSource 详解) GitHub import UIKit let identifier = &quo ...
- Java性能优化的50个细节
在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身.养成良好的编码习惯非常重要,能够显著地提升程序性能. 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时 ...
- Hive(8)-常用查询函数
一. 空字段赋值 1. 函数说明 NVL:给值为NULL的数据赋值,它的格式是NVL( value,default_value).它的功能是如果value为NULL,则NVL函数返回default_v ...
- spark ---词频统计(二)
利用python来操作spark的词频统计,现将过程分享如下: 1.新建项目:(这里是在已有的项目中创建的,可单独创建wordcount项目) ①新建txt文件: wordcount.txt (文件内 ...
- Leecode刷题之旅-C语言/python-206反转链表
/* * @lc app=leetcode.cn id=206 lang=c * * [206] 反转链表 * * https://leetcode-cn.com/problems/reverse-l ...