学习笔记: 组件详解
Vue组件须要注册后才可使用。注册有全局注册和局部注册两种方式。html
全局注册git
Vue.component('my-component', {});
要在父实例中使用这个组件,必需要在实例建立前注册,以后就能够用<my-component></my-component>
的形式来使用组件。github
Vue.component('my-component', { template: `<div>这是一个组件</div>` });
template
的DOM结构必须被一个元素包含,缺乏<div></div>
会没法渲染并报错。设计模式
在Vue实例中,使用components
选项能够局部注册组件,注册后的组件只在该实例做用域下有效。数组
组件中也可使用components
选项来注册组件,使组件能够嵌套。浏览器
var Child = { template: `<div>局部注册组件的内容</div>` }; new Vue({ el: '#app', components: { 'my-component': Child }, });
Vue组件的模板在某些状况下会受到HTML的限制,好比<table>
内规定只容许是<tr>
、<td>
、<th>
等这些表格元素,因此在<table>
内直接使用组件时无效的。这种状况下,可使用特殊的is
属性来挂载组件。缓存
<div id="app"> <table> <tbody is="my-component"></tbody> </table> </div> Vue.component('my-component', { template: `<div>这里是组件内容</div>` });
常见的限制元素还有<ul>
、<ol>
、<select>
。服务器
除了template
选项外,组件中还能够像Vue实例那样使用其余的选项,好比data
、computed
、methods
等。app
可是在使用data
时,data
必须是函数,而后将数据return
出去。frontend
JavaScript对象是引用关系,若是return
的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。
props
传递数据组件不只要把模板的内容进行复用,更重要的是组件间进行通讯。
一般父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收后根据参数的不一样渲染不一样的内容或者执行操做。这个正向传递数据的过程经过props
来实现。
在组件中,使用选项props
声明须要从父级接收的数据,props
的值能够是两种,一种是字符串数组,一种是对象。
<my-component message="来自父组件的数据"></my-component> props: ['message'], template: `<div>{{message}}</div>`,
props
中声明的数据与组件data
函数中return
的数据主要区别就是props
的数据来自父级,而data
中的是组件本身的数据,做用域是组件自己,这两种数据均可以在模板template
及计算属性computed
和方法methods
中使用。
因为HTML特性不区分大小写,当使用DOM模板时,驼峰命名的props
名称要转为短横线分割命名。
<my-component warning-text="提示信息"></my-component>
有时候,传递的数据并非直接写死,而是来自父级的动态数据,这时可使用指令v-bind
动态绑定props
的值,当父组件的数据变化时,也会传递子组件。
<div id="app"> <input type="text" v-model="parentMessage"> <my-component :message="parentMessage"></my-component> </div> props: ['message'], template: `<div>{{message}}</div>`, data: { parentMessage: '' }
这里用v-model
绑定了父级的数据parentMessage
,当经过输入框任意输入时,子组件接收到的props["message"]
也会实时响应,并更新组件模板。
业务中会常常遇到两种须要改变prop
的状况,一种是父组件传递初始值进来,子组件将它做为初始值保存起来,在本身的做用域下能够随意使用和修改。这种状况能够在组件data
内再声明一个数据,引用父组件的prop
。
<div id="app"> <my-component :init-count="1"></my-component> </div> Vue.component('my-component', { props: ['initCount'], template: `<div>{{count}}</div>`, data() { return { count:this.initCount } } });
组件中声明了数据count
,它在组件初始化时会获取来自父组件的initCount
,以后就与之无关了,只用维护count
,这样就能够避免直接操做initCount
。
另外一种状况就是prop
做为须要被转变的原始值传入,这种状况用计算属性就能够。
<div id="app"> <my-component :width="100"></my-component> </div> Vue.component('my-component', { props: ['width'], template: `<div :style="style">组件内容</div>`, computed: { style: function () { return { width: this.width + 'px' } } } });
由于用CSS传递宽度要带单位(px),数值计算通常不带单位,因此统一在组件内使用计算属性。
在JavaScript中对象和数组时引用类型,指向同一个内存空间,因此
props
是对象和数组时,在子组件内改变是会影响父组件。
当prop
须要验证时,须要对象写法。
通常当组件须要提供给别人使用时,推荐都进行数据验证。好比某个数据必须是数字类型,若是传入字符串,就会在控制台弹出警告。
<p data-height="565" data-theme-id="0" data-slug-hash="WywyjV" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="prop" class="codepen">See the Pen prop by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
验证的type
类型能够是:
String
Number
Boolean
Object
Array
Function
type
也能够是一个自定义构造器,使用instanceof
检测。
组件关系可分为父组件通讯、兄弟组件通讯、跨级组件通讯。
当子组件须要向父组件传递数据时,就要用到自定义事件。
v-on
除了监听DOM事件外,还能够用于组件之间的自定义事件。
JavaScript的设计模式——观察者模式方法:
dispatchEvent
addEventListener
Vue组件的子组件用$emit()
来触发事件,父组件用$on()
来监听子组件的事件。
父组件也能够直接在子组件的自定义标签上使用v-on
来监听子组件触发的自定义事件。
<p data-height="365" data-theme-id="0" data-slug-hash="ZRWjKv" data-default-tab="js,result" data-user="whjin" data-embed-version="2" data-pen-title="自定义事件" class="codepen">See the Pen 自定义事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在改变组件的data "counter"
后,经过$emit()
再把它传递给父组件,父组件用@increase
和@reduce
。$emit()
方法的第一个参数是自定义事件的名称。
除了用v-on
在组件上监听自定义事件外,也能够监听DOM事件,这时能够用.native
修饰符表示监听时一个原生事件,监听的是该组件的根元素。
<my-component @click:native="handleClick"></my-component>
v-model
Vue能够在自定义组件上使用v-model
指令。
<my-component v-model="total"></my-component>
组件$emit()
的事件名时特殊的input
,在使用组件的父级,并无在<my-component>
上使用@input="handler"
,而是直接用了v-model
绑定的一个数据total
。
<my-component @input="handleGetTotal"></my-component>
v-model
还能够用来建立自定义的表单输入组件,进行数据双向绑定。
<p data-height="365" data-theme-id="0" data-slug-hash="zaqJBQ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="v-model双向绑定" class="codepen">See the Pen v-model双向绑定 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
实现这样一个具备双向绑定的v-model
组件要知足下面两个要求:
value
属性value
时触发input
事件在实际业务中,除了父子组件通讯外,还有不少非父子组件通讯的场景,非父子组件通常有两种,兄弟组件和跨多级组件。
在Vue 1.x版本中,除了$emit()
方法外,还提供了¥dispatch()
和$broadcast()
。
$dispatch()
用于向上级派发事件,只要是它的父级(一级或多级以上),均可以在Vue实例的events
选项内接收。
此实例只在Vue 1.x版本中有效:
<p data-height="365" data-theme-id="0" data-slug-hash="pKyOOY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="dispatch派发事件" class="codepen">See the Pen dispatch派发事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
$broadcast()
是由上级向下级广播事件,用法彻底一致,方向相反。
这两种方法一旦发出事件后,任何组件均可以接收到,就近原则,并且会在第一次接收到后中止冒泡,除非返回true
。
这些方法在Vue 2.x版本中已废弃。
在Vue 2.x中,推荐任何一个空的Vue实例做为中央事件总线(bus
),也就是一个中介。
<p data-height="365" data-theme-id="0" data-slug-hash="dKMgvJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-bus事件总线" class="codepen">See the Pen Vue-bus事件总线 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
首先建立了一个名为bus
的空的Vue实例;而后全局定义了组件component-a
;最后建立了Vue实例app
。
在app
初始化时,也就是在生命周期mounted
钩子函数里监听了来自bus
的事件on-message
,而在组件component-a
中,点击按钮后会经过bus
把事件on-message
发出去。此时app
就会接收到来自bus
的事件,进而在回调里完成本身的业务逻辑。
这种方法巧妙而轻量地实现了任何组件间的通讯,包括父子、兄弟、跨级。
若是深刻使用,能够扩展bus
实例,给它添加data
、methods
、computed
等选项,这些都是能够公用的。
在业务中,尤为是协同开发时很是有用,由于常常须要共享一些通用的信息,好比用户登陆的昵称、性别、邮箱等,还有用户的受权token
等。
只需在初始化时让bus
获取一次,任什么时候间、任何组件就能够从中直接使用,在单页面富应用(SPA)中会很实用。
除了中央事件总线bus
外,还有两种方法能够实现组件间通讯:父链和子组件索引。
在子组件中,使用this.$parent
能够直接访问该组件的父实例或组件,父组件也能够经过this.$children
访问它全部的子组件,并且能够递归向上或向下无限访问,直到根实例或最内层的组件。
<div id="app"> <p>{{message}}</p> <component-a></component-a> </div> Vue.component('component-a', { template: `<button @click="handleEvent">经过父链直接修改数据</button>`, methods: { handleEvent: function () { this.$parent.message = '来自组件component-a的内容' } } }); var app = new Vue({ el: '#app', data: { message: '' } });
尽管Vue容许这样操做,但在业务中,子组件应该尽量地避免依赖父组件的数据,更不该该去主动修改它的数据,由于这样使得父子组件紧耦合,只看父组件,很难理解父组件的状态,由于它可能被任意组件修改,理想状态下,只有组件本身能修改它的状态。
父子组件最好仍是经过props
和$emit()
来通讯。
当子组件较多时,经过this.$children
来遍历出须要的一个组件实例是比较困难的,尤为是组件动态渲染时,它们的序列是不固定的。
Vue提供了子组件索引的方法,用特殊的属性ref
来为子组件指定一个索引名称。
<p data-height="365" data-theme-id="0" data-slug-hash="dKMLXY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$refs" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/dKMLXY/">Vue-$refs</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在父组件模板中,子组件标签上使用ref
指定一个名称,并在父组件内经过this.$refs
来访问指定名称的子组件。
$refs
只在组件渲染完成后才填充,而且它是非响应式的。它仅仅做为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs
。
Vue 2.x将v-el
和v-ref
合并成ref
,Vue会自动去判断是普通标签仍是组件。
slot
分发内容当须要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot
,这个过程叫作内容分发。
<app>
组件不知道它的挂载点会有什么内容。挂载点的内容是由<app>
的父组件决定的。<app>
组件极可能有它本身的模板。props
传递数据、events
触发事件和slot
内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成。
父组件中的模板:
<child-component> {{message}} </child-component>
这里的message
就是一个slot
,可是它绑定的是父组件的数据,而不是组件<child-component>
的数据。
父组件模板的内容是在父组件做用域内编译,子组件模板的内容是在子组件做用域内编译。
<div id="app"> <child-component v-modle="showChild"></child-component> </div> Vue.component('child-component', { template: `<div>子组件1</div>`, }); var app = new Vue({ el: '#app', data: { showChild: true } });
这里的状态showChild
绑定的是父组件的数据。
在子组件上绑定数据:
<div id="app"> <child-component></child-component> </div> Vue.component('child-component', { template: `<div v-model="showChild">子组件</div>`, data() { return { showChild: true } } }); var app = new Vue({ el: '#app', });
所以,slot
分发的内容,做用域是在父组件上。
slot
在子组件内使用特殊的<slot>
元素就能够为这个组件开启一个slot
(插槽),在父组件模板里,插入在子组件标签内的全部内容将替代子组件的<slot>
标签及它的内容。
<div id="app"> <child-component> <p>分发的内容</p> <p>更多分发的内容</p> </child-component> </div> Vue.component('child-component', { template: ` <div> <slot> <p>若是没有父组件插入内容,我将做为默认出现。</p> </slot> </div> `, }); var app = new Vue({ el: '#app', });
子组件child-component
的模板内定义了一个<slot>
元素,而且用一个<p>
做为默认的内容,在父组件没有使用slot
时,会渲染这段默认的文本;若是写入了slot
,就会替换整个<slot>
。
子组件
<slot>
内的备用内容,它的做用域是子组件自己。
Slot
给<slot>
元素指定一个name
后能够分发多个内容,具名slot
能够与单个slot
共存。
<p data-height="265" data-theme-id="0" data-slug-hash="RJRVQJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-slot" class="codepen">See the Pen Vue-slot by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
子组件内声明了3个<slot>
元素,其中在<div class="main">
内的<slot>
没有使用name
特性,它将做为默认slot
出现,父组件没有使用slot
特性的元素与内容都将出如今这里。
若是没有指定默认的匿名slot
,父组件内多余的内容都将被抛弃。
在组合使用组件时,内容分发API相当重要。
做用域插槽是一种特殊的slot
,使用一个能够复用的模板替换已渲染元素。
<div id="app"> <child-component> <template scope="props"> <p>来自父组件的内容</p> <p>{{props.msg}}</p> </template> </child-component> </div> Vue.component('child-component', { template: ` <div class="container"> <slot msg="来自子组件的内容"></slot> </div> `, }); var app = new Vue({ el: '#app', });
子组件的模板,在<slot>
元素上有一个相似props
传递数据给组件的写法msg="xxx"
,将数据传递到插槽。
父组件中使用了<template>
元素,并且拥有一个scope="props"
的特性,这里的props
是一个临时变量。
template
内能够经过临时变量props
访问来自子组件插槽的数据msg
。
做用域插槽更具表明性的用例是列表组件,容许组件自定义应该如何渲染列表每一项。
<div id="app"> <my-list :book="books"> <!--做用域插槽也能够是具名的Slot--> <template slot="book" scope="props"> <li>{{props.bookName}}</li> </template> </my-list> </div> Vue.component('my-list', { props: { books: { type: Array, default: function () { return []; } } }, template: ` <ul> <slot name="book" v-for="book in books" :book-name="book.name"></slot> </ul> `, });
子组件my-list
接收一个来自父级的prop
数组books
,而且将它在name
为book
的slot
上使用v-for
指令循环,同时暴露一个变量bookName
。
做用域插槽的使用场景是既能够复用子组件的slot
,又可使slot
内容不一致。
slot
Vue 2.x提供了用来访问被slot
分发的内容的方法$slots
。
<p data-height="365" data-theme-id="0" data-slug-hash="vrKZew" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$slots" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/vrKZew/">Vue-$slots</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
经过$slots
能够访问某个具名slot
,this.$slots.default
包括了全部没有被包含在具名slot
中的节点。
给组件设置name
选项,组件在它的模板内能够递归地调用本身。
<div id="app"> <child-component :count="1"></child-component> </div> Vue.component('child-component', { name: 'child-component', props: { count: { type: Number, default: 1 } }, template: ` <div class="child"> <child-component :count="count+1" v-if="count<3"></child-component> </div> `, });
组件递归使用能够用来开发一些具备未知层级关机的独立组件,好比级联选择器和树形控件等。
组件的模板通常都是在template
选项内定义的,Vue提供了一个内联模板的功能,在使用组件时,给组件标签使用inline-template
特性,组件就会把它的内容当作模板,而不是把它当内容分发,这让模板更灵活。
<p data-height="265" data-theme-id="0" data-slug-hash="OEXjLb" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-inline-template" class="codepen">See the Pen Vue-inline-template by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在父组件中声明的数据message
和子组件中声明的数据msg
,两个均可以渲染(若是同名,优先使用子组件的数据)。这是内联模板的缺点,就是做用域比较难理解,若是不是很是特殊的场景,建议不要轻易使用内联模板。
Vue.js提供了一个特殊的元素<component>
用来动态地挂载不一样的组件,使用is
特性来选择要挂载的组件。
<p data-height="365" data-theme-id="0" data-slug-hash="zaBdyY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-component" class="codepen">See the Pen Vue-component by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
能够直接绑定在组件对象上:
<div id="app"> <component :is="currentView"></component> </div> var Home = { template: `<p>Welcome home!</p>` }; var app = new Vue({ el: '#app', data: { currentView: Home } });
Vue.js容许将组件定义为一个工厂函数,动态地解析组件。
Vue.js只在组件须要渲染时触发工厂函数,而且把结果缓存起来,用于后面的再次渲染。
<div id="app"> <child-component></child-component> </div> Vue.component('child-component', function (resolve, reject) { window.setTimeout(function () { resolve({ template: `<div>我是异步渲染的!</div>` }) }, 1000) }); var app = new Vue({ el: '#app', });
工厂函数接收一个resolve
回调,在收到从服务器下载的组件定义时调用。也能够调用reject(reason)
指示加载失败。
$nextTick
异步更新队列
Vue在观察到数据变化时并非直接更新DOM,而是开启一个队列,并缓冲在同一个事件循环中发生的全部数据变化。在缓冲时会去除重复数据,从而避免没必要要的计算和DOM操做。而后,在一下个事件循环tick
中,Vue刷新队列并执行实际(已去重的)工做。
Vue会根据当前浏览器环境优先使用原生的Promise.then
和MutationObserver
,若是都不支持,就会采用setTimeout
代替。
$nextTick
就是用来肯定何时DOM更新已经完成。
<p data-height="365" data-theme-id="0" data-slug-hash="RJRjgm" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$nextTick" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/RJRjgm/">Vue-$nextTick</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
X-Templates
Vue提供了另外一种定义模板的方式,在<script>
标签中使用text/x-template
类型,而且指定一个id
,将这个id
赋给template
。
<div id="app"> <my-component></my-component> <script type="text/x-template" id="my-component"> <div>这是组件的内容</div> </script> </div> Vue.component('my-component', { template: `#my-component`, }); var app = new Vue({ el: '#app', });
在一些很是特殊的状况下,须要动态地建立Vue实例,Vue提供了Vue.extend
和$mount
两个方法来手动挂载一个实例。
Vue.extend
是基础Vue构造器,建立一个“子类”,参数是一个包含组件选项的对象。
若是Vue实例在实例化时没有收到el
选项,它就处于“未挂载”状态,没有关联的DOM元素。可使用$mount
手动地挂载一个未挂载的实例。这个方法返回实例自身,于是能够链式调用其余实例方法。
<p data-height="265" data-theme-id="0" data-slug-hash="BVzmbL" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$mount" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/BVzmbL/">Vue-$mount</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
除了以上写法外,还有两种写法:
new MyComponent().$mount("#app"); new MyComponent({ el: '#app' })
手动挂载实例(组件)是一种比较极端的高级用法,在业务中几乎用不到,只在开发一些复杂的独立组件时可能会使用。