Vue组件
1 组件基础
1.1 什么是组件
组件的概念
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。
如何理解组件
简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为什么要使用组件
举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。
而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。
<calendar></calendar>
我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。
有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。
1.2 创建组件
注册组件
我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件
Vue.component(componentName, {
//选项
})
对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。
组件的选项
-
与创建Vue示例时的选项相同(除了一些根级特有的选项)
-
一个组件的 data 选项必须是一个函数 (每个组件实例具有自己的作用域,组件复用不会互相影响)
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件的使用
<div id="components-demo">
<button-counter></button-counter>
</div>
组件可以复用
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue组件</title> </head> <body> <div id="app"> <h1>{{ title }}</h1> <hr> <!--组件的使用--> <!-- 组件的好处可以将组件当做一个自定义元素重复的复用 --> <!-- 注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。 --> <my-button></my-button> <my-button></my-button> <my-button>小莉莉</my-button> </div> <script src="../dist/js/vue.js"></script> <script> //注册组件 // 注册组件要在创建实例之前进行,向将组件绑定给Vue,传入组件名和对象 // 组件是可复用的 Vue 实例,且带有一个名字 // 因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。 Vue.component('my-button', { data: function() { //组件中的data多了一个function(),一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝 return { counter: 0, //设定初始时的按钮的内显示的值为0 // title:'hello', //会被根组件中的相同数据属性的值覆盖 message:[1,2,3,4] } }, methods: { addCounter() { //在methods中定义一个方法,目的是让按钮绑定的事件在触发的时候执行该方法,进行加1的操作 this.counter ++ } }, // 字符串模板 template: ` <button @click="addCounter">被点了{{ counter }}次</button> `, // 构造函数会在实例创建完成后立即被调用,在控制台可以显示结果 created() { console.log('啊,我被创建了') //组件名被复用了三次,所以在控制台可以看到被执行了三次 } }) //创建根实例 let app = new Vue({ el: '#app', //根实例,挂载元素是必须的 //template: `<h2>HELLO WORLD</h2>` //如果指定了 template,挂载元素会被替换(也就是元素id="app"内的所有元素都会被字符串模板元素替换) data: { title:"Vue组件 同志交友" } }) </script> </body> </html>
组件模板
每个组件模板必须只有一个根元素
模板的形式:
-
template 选项指定字符串 (模板字符串)
-
单文件组件(.vue)
-
内联模板 (不推荐)
<my-component inline-template> <div> <p>These are compiled as the component's own template.</p> <p>Not parent's transclusion content.</p> </div> </my-component>
-
X-Templates
<script type="text/x-template" id="hello-world-template">
Vue.component('hello-world', { template: '#hello-world-template' })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>vue 组件模板</title> </head> <body> <div id="app"> <my-button></my-button> <br> <!-- 将组件设置为内联模板 --> <!-- 当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。 --> <my-input inline-template> <div> <label for="">{{ title }}</label> <input type="text"> </div> </my-input> <br> <my-panel></my-panel> <br> <my-heading></my-heading> </div> <script type="text/x-template" id="my-panel-template"> <div class="panel"> <h2>{{ title }}</h2> <p>{{ content }}</p> </div> </script> <script src="../dist/js/vue.js"></script> <script> //注册组件 //1、字符串模板 Vue.component('my-button', { // 字符串模板-----创建一个按钮 template: ` <button>my-button</button> ` }); //注册组件 //2、内联模板 不建议使用 Vue.component('my-input', { data: function() { return { title:'用户名' } } }); //注册组件 //3、x-templates 模板 // 另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去 Vue.component('my-panel', { data: function(){ return { title: '我的小莉莉', content: '啊,小莉莉,啊啊啊,啊啊啊' } }, template:'#my-panel-template' //指向一个id,并将定义的属性的值传过去 }); //注册组件 // Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器。 Vue.component('my-heading', { render: function(createElement) { return createElement('h2', 'HELLO'); //创建元素,第一个参数为标签名,第二个参数为标签内的内容 } }) //创建根实例,并将根实例挂载到一个元素上 /*let app = new Vue({ }) app.$mount('#app')*/ new Vue({ }).$mount('#app') </script> </body> </html>
全局组件和局部组件
使用 Vue.component()
注册的组件都是全局组件
我们可以定义局部组件
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app'
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>全局组件和局部组件</title> </head> <body> <div id="app"> <h1>全局组件和局部组件</h1> <hr> <!--该组件 就是 根组件的 子组件,虽然是全局组件,应该组件包含根组件挂载的元素内--> <my-panel></my-panel> <!--也是根组件的子组件--> <my-button></my-button> <my-textarea></my-textarea> <my-input></my-input> </div> <script src="../dist/js/vue.js"></script> <script> //注册组件 //1、全局组件 作用域是全局 ,在哪都能用 // 该组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中 Vue.component('my-panel', { // 字符串模板 template: ` <div style="border:1px solid #ccc"> <h2>my-panel</h2> <p>Lorem ipsum dolor sit amet.</p> <my-textarea></my-textarea> //因为my-teatarea是全局组件,所以在哪都可以用 </div> `, // 组件内任然可以定义多个组件 components: { //定义 n 多组件 } }); // 组件my-panel包含组件my-textarea组件,但my-textarea也是全局组件,即在哪都可以用 Vue.component('my-textarea', { template: ` <textarea></textarea> ` }) //定义根实例 //根组件 new Vue({ el:'#app', //2、局部组件加s,可以包含多个组件 -----注意局部注册的组件在其子组件中不可用 // 局部组件是一个对象,key对应的是组件名和字符串模板名,局部组件名可以在挂载的HTML元素中直接当做自定义双标签直接使用 components: { //对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。 'my-button': { data:function(){ return { counter: 0 } }, // 字符串模板 template: ` <button>{{counter}}</button> ` }, // 字符串模板---局部组件 'my-input': { template:` <input type="text" style='background:pink'> ` } } }) </script> </body> </html>
2. 组件之间的嵌套使用和互相通信
组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。
每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。
2.1 通过Prop向子组件传递数据
基本使用
在子组件中声明 prop,然后添加一个 message
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。我们能够在组件实例中访问这个值,
然后直接传入值就可以在子组件中使用 message。
<child message="hello!"></child>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件通信</title> <link rel="stylesheet" href="../dist/css/bootstrap.css"> </head> <body> <div id="app"> </div> <script src="../dist/js/vue.js"></script> <script> //注册组件 Vue.component('thumb', { data: function(){ return {} }, //props中定义的属性 根 data中定义的数据 具有相同的地位 // 数组形式的props /*props: ['productSrc', 'productName', 'productDes', 'productPrice'],*/ /*props: { productSrc: String, productName: String, productDes: String, productPrice: Number },*/ /*props: { productSrc: { type: String, required: true }, productName: { type:String, default:'8848手机' }, productDes: { type: String, required: true, validator: function(val){ return val.length <= 20; } }, productPrice: [String, Number] },*/ // 通常我们希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型: //props中定义的属性 根 data中定义的数据 具有相同的地位 props: { product: { type: Object, //对象 required: true } }, // 字符串模板----引用了bootstrap.css template: ` <div class="col-xs-6 col-sm-4 col-md-3"> <div class="thumbnail"> <img :src="product.productSrc" alt="..."> <div class="caption"> <h3>{{ product.productName }}</h3> <p>{{ product.productDes }}</p> <p style='color:red'>{{ product.productPrice }}</p> <p><a href="#" class="btn btn-primary" role="button">直接购买</a> <a href="#" class="btn btn-default" role="button">加入购物车</a></p> </div> </div> </div> ` }); //创建根实例 //根组件 // new Vue({参数1,参数,参数3,..} new Vue({ el:'#app', data: { // 创建数据 productList: [ {productName:'纯金手机', productSrc:'../dist/images/product01.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'}, {productName:'纯银手机', productSrc:'../dist/images/product02.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'}, {productName:'纯铜手机', productSrc:'../dist/images/product03.png', productDes:'非常好用的手机,牛逼', productPrice:'998元'}, {productName:'纯铁手机', productSrc:'../dist/images/product04.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'}, ] }, // 字符串模板 template: ` <div class="container"> <div class="page-header"> <h1>组件通信</h1> </div> <div class="row"> <thumb v-for='item in productList' :product="item" :key='item.productName'></thumb> </div> </div> `, // el:'#app', 挂载元素也可以写在创建实例参数的后面 }) </script> </body> </html>
父组件向子组件通信
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>同志加油</title> </head> <body> <div id="app"></div> <script type="text/x-template" id="root-template"> <div class="container"> <child-component :message="message"></child-component> </div> </script> <script type="text/x-template" id="child-template"> <div class="box"> <p>{{ message }}</p> </div> </script> <script src="../dist/js/vue.js"></script> <script> //定义 注册 //组件 注册在Vue 类中 Vue.component('child-component', { props: ['message'], //根data 中的属性 地位相同 template: '#child-template' }) //根组件 根实例 let app = new Vue({ el:'#app', data:{ message:"HELLO 同志", }, template:'#root-template' }); /* 给类添加东西 实例化 new 类 a 在给类添加东西 */ </script> </body> </html>
Prop 的大小写
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
传入一个对象的所有属性
<blog-post v-bind="post"></blog-post>
等价于
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
Prop验证
我们可以为组件的 prop 指定验证要求
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 匹配任何类型)
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
}
}
}
})
类型列表:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 自定义的构造函数
2.2 通过事件向父级组件发送消息
on(eventName)+on(eventName)+emit(eventName) 实现通讯
在父组件中使用 on(eventName)监听事件,然后在子组件中使用on(eventName)监听事件,然后在子组件中使用emit(eventName) 触发事件,这样就能实现子组件向父组件传值。
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
console.log('第'+this.total+'次点击')
}
}
})
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>子组件向父组件通信</title> <style> p { font-size:100px; margin: 5px 0px; } </style> </head> <body> <div id="app"> <h1>{{ title }}</h1> <hr> <p>{{ count }}</p> <hr> <button-count @add="addFn($event)" @del="delFn"></button-count> </div> <script src="../dist/js/vue.js"></script> <script> //注册组件 Vue.component('button-count', { methods: { addCount() { //向父组件 发送事件 this.$emit('add', 2) }, deleteCount(){ this.$emit('del', 4) } }, template: ` <div> <button @click="addCount()">增加2</button> <button @click="deleteCount()">减少4</button> </div> `, created() { this.$emit('add', 2) } }) //创建根实例 //根组件 let app = new Vue({ el: '#app', data: { title:'同志交友', count: 0 }, methods: { addFn(val){ this.count += val; }, delFn(val) { this.count -= val; } } }) </script> </body> </html>
子组件向父组件通信
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用 $emit
的第二个参数来提供这个值
incrementCounter: function () {
this.counter += 1
this.$emit('increment', this.counter)
}
然后当在父级组件监听这个事件的时候,我们可以通过 $event
访问到被抛出的这个值
<button-counter v-on:increment="postFontSize + $event"></button-counter>
或者,如果这个事件处理函数是一个方法:那么这个值将会作为第一个参数传入这个方法:
<button-counter v-on:increment="incrementTotal"></button-counter>
methods: {
incrementTotal: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在组件上使用 v-model
组件内input需要满足条件:
- 将其
value
特性绑定到一个名叫value
的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的input
事件抛出
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
v-model
在组件上的使用
<custom-input v-model="searchText"></custom-input>
<!-- 上面的写法 等价于 下面的写法 -->
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
3 插槽 slot
3.1 通过插槽分发内容
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
<alert-box>
Something bad happened.
</alert-box>
·Something bad happened.· 会替换掉 slot标签
3.2 模板中多个插槽
组件模板
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
调用组件
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
3.3 插槽默认内容
<button type="submit">
<slot>Submit</slot>
</button>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件插槽</title> </head> <body> <div id="app"> <h1>组件插槽</h1> <hr> <!-- 组件会被加上字符模板template的样式 --> <my-article>HELLO WORLD</my-article> <my-article>HELLO 小莉莉</my-article> <my-article> <button>按钮</button> <a href="#">超链接</a> </my-article> <hr> <layout> <h1 slot="page-header">同志你好</h1> <p>Lorem ipsum dolor sit amet.</p> <p>Lorem ipsum dolor sit amet.</p> <p slot="page-footer">footer</p> <p>Lorem ipsum dolor sit amet.</p> <p>Lorem ipsum dolor sit amet.</p> </layout> </div> <script src="../dist/js/vue.js"></script> <script> //注册组件 Vue.component('my-article', { template: ` <div style='border:1px solid #ccc;width:600px'> <p> <slot></slot> //相当于占位符组件中的内容会直接替换插槽显示 </p> </div> ` }); //注册组件 Vue.component('layout', { template: ` <div> <header> <slot name="page-header"></slot> //指定插槽的名字会寻找有该名字的插槽,并将其替换 </header> <main> <slot></slot> //该插槽没有指定名字,会被没有插槽名字的p元素替换 </main> <footer> <slot name="page-footer">page-footer默认内容</slot> </footer> </div> ` }) //创建根实例 // 挂载中元素在页面中的显示顺序取决于字符模板中元素的顺序 new Vue({ el:'#app' }) </script> </body> </html>
4. 动态组件
4.1 实现动态组件
在不同组件之间进行动态切换
<component is="组件名" class="tab"></component>
实现选项卡案例
4.2 在动态组件上使用 keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
主要用于保留组件状态或避免重新渲染
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>动态组件</title> <link rel="stylesheet" href="../dist/css/bootstrap.css"> <style> .panel { border-top: none; } </style> </head> <body> <div id="app"> <div class="container"> <div class="page-header"> <h1>动态组件</h1> </div> <div class="row"> <div class="col-md-6"> <ul class="nav nav-tabs"> <!-- 循环数据属性对应的数组内的对象,通过触发事件来显示选项卡表标题下的内容,点击会得到对应的索引值 --> <li v-for="item,index in tabs" :class="{active:isTab(index)}" @click="setTab(index)"><a href="javascript:0">{{ item.tabName }}</a></li> <!--item.tabNam传入选项卡的标题--> </ul> <div class="panel"> <div class="panel-body"> <!-- <component is="组件名" class="tab"></component>在不同组件之间进行动态切换 --> <keep-alive> <!--实现缓存变化频繁是要加上--> <component :is="tabs[tab].tabComponent"></component> <!--进行组件的动态切换,通过数组索引和点key拿到组件的名字,从而显示组件中的字符模板中的元素内容--> </keep-alive> </div> </div> </div> </div> </div> </div> <script src="../dist/js/vue.js"></script> <script> //创建根实例 // 动态组件的好处,我们添加组件只需要在数组中和组件在中添加字符串模板即可 new Vue({ el:'#app', data: { tabs: [ {'tabName':'登录', 'tabComponent':'login-component'}, {'tabName':'注册', 'tabComponent':'register-component'}, {'tabName':'免登录', 'tabComponent':'no-login-component'}, {'tabName':'商品列表', 'tabComponent':'product-component'}, {'tabName':'未付款商品', 'tabComponent':'no-pay-component'}, {'tabName':'已付款商品', 'tabComponent':'payed-component'}, ], tab: 0 //默认是显示登录选项卡中的内容 }, methods: { isTab(index) { return this.tab === index; //通过返回布尔值的真假来决定class类知否显示 alert('OK') }, setTab(index) { this.tab = index; } }, // 选项卡标题下对应的组件内容 components: { 'login-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" class="form-control" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" class="form-control" /> </div> <button class="btn btn-default btn-block">登录</button> </form> ` }, 'register-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" class="form-control" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" class="form-control" /> </div> <div class="form-group"> <label for="#">确认密码</label> <input type="password" class="form-control" /> </div> <button class="btn btn-primary btn-block">注册</button> </form> ` }, 'no-login-component': { template:` <div> 我是李刚,我不用登录,我牛逼 </div> ` }, 'product-component': { template:` <div> 苹果手机<br>mac笔记本<br>香奈儿<br>兰博基尼<br>特斯拉<br>阿尼玛 </div> ` }, 'no-pay-component': { template:` <div> 华为手机<br>mac笔记本<br>香奈儿<br>路虎<br>特斯拉<br>秋裤 </div> ` }, 'payed-component': { template:` <div> 苹果手机<br>mac-pro笔记本<br>瑞士手表<br>宾利<br>特斯拉<br>林肯加长版 </div> ` } } }) </script> </body> </html>
动态组件应用:选项卡
4.3 绑定组件选项对象
动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的示例
var tabs = [
{
name: 'Home',
component: {
template: '<div>Home component</div>'
}
},
{
name: 'Posts',
component: {
template: '<div>Posts component</div>'
}
},
{
name: 'Archive',
component: {
template: '<div>Archive component</div>',
}
}
]
new Vue({
el: '#dynamic-component-demo',
data: {
tabs: tabs,
currentTab: tabs[0]
}
})
<component
v-bind:is="currentTab.component"
class="tab"
>
</component>
5 组件的其他特性
5.1 解析 DOM 模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
<table>
<blog-post-row></blog-post-row>
</table>
上面的写法,渲染效果会不甚理想,可以采用以下写法
<table>
<tr is="blog-post-row"></tr>
</table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
<script type="text/x-template">
5.2 Prop的一些问题
Prop的属性名问题
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
如果你使用字符串模板,那么这个限制就不存在了。
非Prop属性
组件上定义的非Prop属性 会传递到 组件模板的根元素上
class 和 style 特性会非常智能,即两边的值会被合并起来
对prop重新赋值
子组件中,对prop重新赋值,会报警告
5.3 组件事件的相关问题
将原生事件绑定到组件
想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符
<base-input v-on:focus.native="onFocus"></base-input>
.sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”
推荐以 update:my-prop-name 的模式触发事件
//子组件中
this.$emit('update:title', newTitle)
<!-- 上级组件 模板中 -->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
以上写法可以换成下列写法
<text-document v-bind:title.sync="doc.title"></text-document>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue组件注意事项</title> </head> <body> <div id="app"> <h1>组件</h1> <hr> <table border="1" width="600"> <div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A explicabo minima labore nam earum odit asperiores atque, debitis distinctio praesentium reprehenderit, voluptate rerum beatae molestiae dolorum, quis eaque excepturi nemo.</div> <tr is="table-row"></tr> <tr is="table-row"></tr> <tr is="table-row"></tr> </table> <hr> <!--给组件 指定 class和style 会继承到 组件模板的根元素上--> <my-button style="border:1px solid green" @click.native="clickFn"></my-button> <!--实现prop的双向数据绑定--> <hr> <my-input :msg="message" @update:msg='message = $event'></my-input> <my-input :msg.sync="message"></my-input> <h2>{{message}}</h2> <my-component @update="fn"> </div> <script src="../dist/js/vue.js"></script> <script> new Vue({ el:'#app', data: { message:'HELLO' }, methods: { clickFn(){ alert('OK') }, fn(n1,n2,n3){ } }, components: { 'table-row': { template: ` <tr><td>1</td><td>1</td><td>1</td></tr> ` }, 'my-button': { template: ` <div> //div是组件模板的根元素 <button style='color:red'>按钮</button> <button style='color:red'>按钮</button> </div> ` }, 'my-input': { props: ['msg','title'], template: ` <div> <label for="#">{{ msg }}</label> <input type="text" v-model="msg"> </div> `, watch: { msg(){ this.$emit('update:msg', this.msg) }, title(){ this.$emit('update:title', this.title) } } } }, }) </script> </body> </html>
Vue组件注意事项
5.4 官方文档-组件
深入了解组件
网址: https://cn.vuejs.org/v2/guide/components-registration.html