7-组件详解
7.1 组件与复用
- 注册
- 全局注册
Vue.component('my-component-name',{ template:'<div>模板</div>' }); - 局部注册
var Chile={ template:'<div>模板</div>' }; var app=new Vue({ el:'#app', components:{ 'my-component':Child } });
- 全局注册
- 组件可以使用的选项
-
template -
data- 区别:必须是函数,然后把数据return
data:function(){ return{ message1:'组件内容', message2:'另一个组件内容' } } - 注意:如果
return的对象引用了外部对象,那么数据会共享,所以为了复用组件,需要给组件返回一个新的data独立
- 区别:必须是函数,然后把数据return
-
computed -
methods -
<table>内需要使用特殊的is属性挂在组件
-
7.2 使用props传递数据
组件间通信:
- 正向传递数据:父组件向子组件传递数据或参数
- 接受父级的数据(字符串)
//props 为了 复用+个性化 组件? //需要驼峰命名的props名称转为短横分隔命名 (warningText->warning-text) //my-component 接受来自父级的数据(warning-text),并把他在组件模板中渲染(template) div#app my-component[warning-text="提示信息"] my-component[warning-text="警告信息"] Vue.component->'my-component' props:['warningText'], template:'<div>{{warningText}}</div>'- 动态绑定
props的值,使用来自父级的动态数据(单向)
// 数据来源 input-text: parentMessage --> 父组件绑定属性 bind:message-one: parentMessage --> 渲染 props:messageOne div{{messageOne}} div#app input[type=text][v-model=parentMessage] my-component[:message-one=parentMessage] Vue.component -> 'my-component' props:['messageOne'], template:'<div>{{messageOne}}</div>' app -> new Vue el:'#app', data:{ parentMessage:'' }- 传输 非字符串类型数据(数组、对象、数字)时需要 bind
单向数据流
父组件数据变化时会传递给子组件,但是反过来不行,避免子组件无意中修改父组件的状态
两种需要改变prop的情况
-
接收父组件传来的初始值
//此例中 initCount 是父组件传来初始值的,之后只要维护 count就可以了 div#app my-component[:init-count="1"] Vue.component-> 'my-component' props:['initCount'], template:'<div>{{count}}</div>', data:function(){ return { count:this.initCount } } var app -> new Vue el:'#app' -
作为需要被转变的原始值传入(这种情况用计算属性)
//此例中是 width: 200 -> 200px div#app my-component[:width="200"] Vue.component -> my-component props:['width'] template:'<div :style="style">组件内容<div>' computed:{ style:function(){ return { width:this.width+'px' } } } -
props是对象或数组时,在子组件内改变会影响父组件。(因为是引用类型)
7.2.3 数据验证
一般组件提供给别人使用时,需要进行数据验证(prop)
- prop数据类型验证示例
- 数字类型
Vue.component -> my-component
props:{
//必须是数字类型
propA:Number,
//必须是字符串或数字类型
propB:[String,Number],
//布尔值,如果没有定义,默认值就是true
propC:{
type:Boolean,
default:true
},
//数字,而且是必传
propD:{
type:Number,
required:true
},
//如果是数组或对象,默认值必须是一个函数来返回
propE:{
type:Array,
default:function(){
return [];
}
},
//自定义一个验证函数
propF:{
validator:function(value){
return value>10;
}
}
//type: String Number Boolean Object Array Function
//type 也可以是一个自定义构造器,使用 instanceof 检测
//prop 验证失败,开发版本下会在控制台抛出一个警告
7.3 组件通信
7.3.1 自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件。
子组件用$emit()触发事件,父组件用$on()来监听子组件的事件。
??父组件也可以直接在子组件的自定义标签上使用 v-on监听子组件触发的自定义事件。
//子组件的: counter && handleIncrease && handleReduce
//通过$emit传递给父组件: this.$emit('crease',this.counter) && this.$emit('reduce',this.counter)
//父组件:my-component[@crease="handleGetTotal"][@reduce="handleGetTotal"]
div#app
p{总数:{{total}}}
my-component[@increase="handleGetTotal"][@reduce="handleGetTotal"]
Vue.component('my-component',{
template:'\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data:function(){
return {
counter:0
}
},
methods:{
handleIncrease:function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter--;
this.$emit('reduce',this.counter);
}
}
});
var app -> new Vue()
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal:function(total){
this.total=total;
}
}
//使用 .native 修饰符监听DOM事件
// <my-component v-on:click.native="handleClick"></my-component>
7.3.2 自定义组件使用v-model
- 直接使用
v-model绑定数据
//父组件 v-model=total
//app -> data:total
//子组件: this.$emit('input',this.counter);
div#app
my-component[v-model=total]
my-component
template: button[@click=handleIncrease]
data: -> -> -> counter:0
methods:
handleIncrease:function(){
this.counter++;
this.$emit('input',this.counter);
}
//相当于 父组件:@input=handleIncrease
// methods: handleIncrease(){...}
- 创建自定义表单输入组件,进行数据双向绑定
- 需要:接收一个
value属性 - 需要:在有新的
value时触发input事件
//实现父组件改变值: total-- //子组件同步改变:counter--? div#app p{总数:{{total}}} my-component[v-model=total] button[@handleReduce]{-1} mycomponent template:'<input :value @input="updateValue">' methods:{ //实际上这个根本没有运行?没有也行? updateValue:function(event){ console.log(this.value); console.log(event.target.value); this.$emit('input',event.target.value); } } app -> new Vue data: total methods: handleReduce - 需要:接收一个
7.3.3 非父子组件通信
- 使用一个空的Vue实例作为中央时间总线(bus).
div#app
{{message}}
<component-a></component-a>
script
var bus = new Vue();
component-a
template:'<button @click="handleEvent"></button>',
methods:
handle:function(){
bus.$emit('on-message','来自子组件的内容')
}
var app = new Vue
data: message ,
mounted:funciton(){
_this=this;
bus.$on('on-message',function(msg){
_this.message=msg;
})
}
//创建一个名为bus的空Vue实例
//全局定义组件 component-a
//点击按钮 会触发事件 onmessage
//创建Vue实例 app
//mounted钩子函数里监听 bus 的事件 on-message
//点击按钮,会通过bus把时间 on-message 发出去
//此时app就会接受到来自bus的事件,进而在回调里完成自己的业务逻辑
-
父链
(子组件使用this.$parent可以直接访问该组件的父实例或组件,父组件可以使用this.$children访问它所有的子组件。并且可以无限的向上或向下访问。)this.$parent
div#app {{message}} component-b component-b template:'button[@click=handleEvent]' methods handleEvent:function(){ this.$parent.message="更改父实例的message" } app = new Vue el:'#app', data:{ message:'' }this.$children- (使用特殊的属性
ref为子组件指定一个索引名称) - this.$refs只在组件渲染完成后才填充,并且是非响应式的
- 应该避免在模板或计算属性中使用
$refs
div#app {{message}} button[@click="handleRef"] component-a[refs='comA'] script component-a data msg:'子组件内容' app el:'app', data: message:'' methods: handleRef:function(){ this.message=this.$refs.comA.msg; }- (使用特殊的属性
7.4 使用slot分发内容
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到 slot, 这个过程叫作内容分发( transclusion)。
给<slot>元素指定一个name后可以分发多个内容,具名Slot可以和单个Slot共存。
如果slot没有使用name属性,那么将作为默认slot出现。如果没有指定匿名的slot,父组件内多余的内容片段都将被抛弃。
子组件里的slot标签可以设置备用内容。
div#app
my-component
h3[slot="header"]
p{匿名的slot}
script
Vue.component 'my-component'
p{一些其他内容}
slot[name="header"]
slot
slot[name="footer"]{我有备用}
app=new Vue
7.4.4 作用域插槽
特殊的slot,使用一个可以复用的模板替换已渲染元素。
div#app
component-a
{父组件的内容}
template[scope="props"]
{{props.message}}
script
Vue.component 'component-a'
slot[msg="来自子组件的内容"]
//子组件内,slot 元素使用 msg="xxx",将数据传到插槽上
//父组件使用 <template> 元素 的 scope="props" ,通过临时变量 props 访问来自子组件插槽的数据 msg。
另一个例子
div#app
my-list[:books=books]
template[slot="book"][scope="props"]
li{{props.bookName}}
script
Vue.component 'my-list'
props:{
books:{
type:Array,
default:function{
return [];
}
}
},
template:`
<ul>
<slot v-for="book in books" :book-name="book.name" name="book"></slot>
</ul>
`
app
el:'app',
data:{
books:[
{name:'第一本书'},
{name:'第2本书'},
{name:'third book'}
]
}
// 数据由子组件提供,样式有父组件设置
7.4.5 访问slot
使用$slots方法访问slot分发的内容。主要用于独立组件的开发。
div#app
h3[slot=header]{标题}
p{默认内容}
Vue.component 'my-component'
template
slot[name=header]
mounted:function(){
var header=this.$slots.header;
var main=this.$slots.default;
console.log(header);
console.log(main[0].elm.innerHTML);
}
app el:'#app'
7.5 组件高级用法
7.5.1 递归组件
组件在它的模板内可以递归地调用自己,只要给组件设置name选项。
可以用来开发一些位未知层级关系的独立组件,比如级连选择器和树形选择器。
7.5.2 内联模板
给组件标签使用inline-template特性,组件就会把它的内容当成模板。
div#app
child-component[inline-template]
h3{在父组件中定义子组件的模板}
{{message}}
{{msg}}
script
Vue.component('child-component',{ })
data:funciton(){
return {
msg:'子组件声明的数据'
}
}
app
el:'#app',
data:{
message:'父组件声明的数据'
}
//使用 vue.js(开发模式), 模板内的父组件数据{{message}}不能渲染
//同名时,优先使用子组件的数据
7.5.3 动态组件
div#app
component[:is="currentView")]
button[@click="handleCurrentView('A')"]{切换到A}
button[@click="handleCurrentView('B')"]{切换到B}
button[@click="handleCurrentView('C')"]{切换到C}
script
app = new Vue()
data:
currentView:'comA'
components:{
'comA':{
template:'<div>组件A</div>'
},
'comB':{
template:'<div>组件B</div>'
},
'comC':{
template:'<div>组件C</div>'
}
},
methods:{
handleCurrentView(component_):function(){
this.currentView='com'+component_;
}
}
//多个模板在 componets 里
//根据值 currentView 来切换模板
//需要一个用户能够改变 currentView 值的按钮。
7.5.4 异步组件
如果组件过多时,一开始就把所有组件都加载是没必要的开销。所以,Vue.js 允许将组件定义为一个工厂函数,动态地解析组件。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果储存起来,用于后面的再次渲染。
Vue.component('my-component',function(resolve,reject){
window.setTimeout(function(){
resolve({
template:'<div>我是异步渲染的</div>'
});
},1000);
}
//1秒后 渲染子组件
// JavaScript - 异步
// funciton test(resolve,reject){...}
// function todo(result){
// return (new Promise(function(resolve,reject){...})
// }
// var p1 = new Promise(test)
// .then(todo)
// .then(function(result){...})
// .catch(function(reason){...});
7.6 其他
7.6.1 $nextTick
Vue观察到数据变化时,不是直接更新DOM,而是开启一个队列,并缓冲在同一时间循环中发生的所有数据变化。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。
然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重)工作。
div#app
div#div[v-if=showDiv]{这是一段文本}
button[@click="getText"]{获取div内容}
script
var appp = new Vue
el:'app'
data:
showDiv:false
method:
getText:function(){
_this=this;
this.showDiv=true;
var text=document.getElementById('div').innerHTML;
console.log(text);
}
//因为还没更新DOM
//所以会出现错误:TypeError: "document.getElementById(...) is null"
//$nextTick 等下一个时间循环tick
// this.$nextTick(function(){ ...getElementById...log... })
7.6.2 X-Templates
另一种定义模板的方式
div#app
my-component
script[type="text/x-template"][id="my-component-"]
div{这是组件的内容}
script
Vue.component('my-component',{
template:'#my-component-'
})
//为了将模板和组件的其他定义隔离
7.6.3 手动挂载实例
一般通过 new Vue()的形式创建实例。
如果需要动态地创建Vue实例,可以使用Vue.extend和$mount两个方法手动挂载一个实例。
div#mount-div
script
//创建
var MyComponent = Vue.extend({
template:'<div>Hello:{{name}}</div>',
data:function(){
return {
name:'Aresn'
}
}
})
//挂载
new MyComponent().$mount('#mount-div');
=> new MyComponent({el:'#mount-div'});
=>var component = new MyComponent().$mount();
document.getElementById('mount-div').appendChild(component.$el);

浙公网安备 33010602011771号