1.使用组件
//组件命名规则:全小写-分隔(kebab-case)
//注册一个全局组件
Vue.component('my-component', {});
//注册一个局部组件
new Vue({
components:{
'my-component':{}
}
});
<table>
<!-- ul,ol,table,select标签限制了被包裹元素的类型,如果使用自定义组件会有问题 -->
<!-- my-row></my-row -->
<!-- 变通方案使用is属性 -->
<tr is="my-row"></tr>
</table>
为什么component的data被设计为一个函数?
嘻嘻...你猜...
2.父子组件通信
父组件通过props向下传递数据给子组件,子组件通过events给父组件发送消息
组件实例作用域是孤立的,不应该在子组件内部直接引用父组件数据,应该通过props选项访问
<!-- myMessage属性camelCase在html中要写成kebab-case -->
<!-- my-message绑定静态字符串 -->
<my-component my-message="hello"></my-component>
<!-- :my-message动态绑定父组件变量,当父组件数据变化,会传导给子组件 -->
<my-component :my-message="message"></my-component>
Vue.component('my-component', {
props:['myMessage'],
template:'<span>{{myMessage}}</span>'
});
var app = new Vue({
el: "#app",
data:{
message:"from parent"
}
});
props是单向绑定,父组件属性变化会传到给子组件,但不会反过来,不应该在子组件内部直接改变prop值
<my-component :init="init" :size="size"></my-component>
Vue.component('my-component', {
props: ['init', 'size'],
template: '<span>{{myInit}}{{mySize}}</span>',
//组件内部改变prop的2种方案:
//1.prop作为值初始值传入,赋值给组件局部变量
data: function () {
return {myInit: this.init}
},
//2.prop作为初始值传入,由组件处理为其他数据输出
computed: {
mySize: function () {
return this.size.toLowerCase();
}
}
});
var app = new Vue({
el: "#app",
data: {
init: 0,
size: "six"
}
});
props如果是引用类型,在子组件内部改变其属性会影响父组件
可为组件props指定验证规则
<my-component :pro-a="proA" :pro-b="proB" :pro-c="proC" :pro-d="proD"></my-component>
Vue.component('my-component', {
props: {
//String,Number,Boolean,Function,Object,Array,Symbol,null(任意类型)
proA: Number,
//多种类型
proB: [String, Number],
proC: {
type: String,
//设置默认值
default: "66",
//设置必填验证
required: true
},
proD: {
type: Number,
//自定义默认值
default: function () {
return 6;
},
//自定义验证规则
validator: function (value) {
return value >= 6;
}
}
},
template: '<span></span>'
});
var app = new Vue({
el: "#app",
data: {
//报错
proA: "6",
//报错
proB: [],
//报错
proC: null,
//报错
proD: 5
}
});
3.自定义事件
子组件通过自定义事件系统于父组件通信
- $on(eventName)监听事件
- $emit(eventName)触发事件
<!-- 父组件可直接使用v-on监听子组件触发的自定义事件 -->
<my-button @myevent="addTotal"></my-button>
<!-- 父组件监听子组件根元素上的原生事件,使用.native修饰符 -->
<my-button @click.native="addTotal"></my-button>
Vue.component('my-button', {
template: '<button @click="addCount">{{count}}</button>',
data: function () {
return {
count: 0
}
},
methods: {
addCount: function () {
this.count++;
//触发自定义事件
this.$emit('myevent');
}
}
});
var app = new Vue({
el: "#app",
data: {
total: 0
},
methods: {
addTotal: function () {
this.total++;
}
}
});
v-model语法糖实际会转化为::value="value" @input="value=$event.target.value",因此,自定义事件可以用来创建自定义表单输入组件,使用v-model实现数据双向绑定
{{currency}}
<!--my-currency :value="currency" @input="currency = $event.target.value"></my-currency-->
<my-currency v-model="currency"></my-currency>
Vue.component('my-currency', {
props: ["value"],
template: '<div><input ref="myInput" :value="value" @input="updateValue"/></div>',
methods: {
updateValue: function (event) {
var currency = "$" + event.target.value.replace(/\$/g, "");
this.$refs.myInput.value = currency;
//触发input事件
this.$emit('input', currency);
}
}
});
var app = new Vue({
el: "#app",
data: {
currency: "0"
}
});
可定制组件的v-model的prop和event,避免冲突
{{currency}}
<!--my-currency :myvalue="currency" @myinput="currency = $event.target.value"></my-currency-->
<my-currency v-model="currency" value="hello"></my-currency>
Vue.component('my-currency', {
//可定制v-model
model: {
prop: 'myvalue',
event: 'myinput'
},
//释放value属性
props: ['myvalue', 'value'],
template: '<div><input ref="myInput" :value="myvalue" @input="updateValue"/></div>',
methods: {
updateValue: function (event) {
var currency = "$" + event.target.value.replace(/\$/g, "");
this.$refs.myInput.value = currency;
//触发myinput事件
this.$emit('myinput', currency);
}
}
});
var app = new Vue({
el: "#app",
data: {
currency: "0"
}
});
非父子组件通信,简单场景使用一个空Vue实例做事件中转,复杂情况请考虑状态管理模式
var bus = new Vue();
bus.$emit('my-event', 6);
bus.$on('my-event', function(arg){});
4.使用Slots分发内容
slots标签让组件内容可以混合嵌套,类似Angular的transclusion
<!-- 最终生成html -->
<!--div>
<h1>child</h1>
<div>parent content</div>
</div-->
<my-component>
<div>parent content</div>
</my-component>
Vue.component('my-component', {
template: '<div><h1>child</h1><slot></slot></div>'
});
为slot指定name来配置如何分发内容
<!-- 最终生成html -->
<!--div>
<h1>header</h1>
<p>footer</p>
</div-->
<my-layout>
<h1 slot="header">header</h1>
<p slot="footer">footer</p>
</my-layout>
Vue.component('my-layout', {
template: '<div><slot name="header"></slot><slot name="footer"></slot></div>'
});
作用域插槽,暴露数据,允许父组件自定义渲染
<my-list :items="items">
<!-- template表示作用域插槽模板,scope为一临时变量,来至子组件 -->
<template slot="listItem" scope="props">
<!-- 自定义渲染 -->
<li>{{props.text}}</li>
</template>
</my-list>
Vue.component('my-list', {
props: ['items'],
template: '<ul><slot name="listItem" v-for="item in items" :text="item"></slot></ul>'
});
var app = new Vue({
el: "#app",
data: {
items: ["1", "2"]
}
});
5.动态组件
通过<component>元素,动态绑定is特性,可使用同一个挂载点,动态切换组件
<component :is="currentView"></component>
<!-- keep-alive保留切换出去的组件在内存,避免重新渲染 -->
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
<button @click="change">change</button>
var myHeader = {
template: '<h1>Header</h1>'
};
var myFooter = {
template: '<div>Footer</div>'
};
var myText = {
template: '<input type="text"/>'
};
var app = new Vue({
el: "#app",
data: {
//currentView: myHeader
currentView: 'myFooter'
},
components: {
myFooter: myFooter,
myText: myText
},
methods: {
change: function () {
this.currentView = (this.currentView == 'myText' ? 'myFooter' : 'myText');
}
}
});
6.杂项
组件分3部分:
- props允许外部传递数据给组件
- events允许外部和组件内部通信
- slots允许外部自定义内容渲染到组件中
子组件索引
<!-- ref为子组件指定索引,用来在javascript中直接访问子组件 -->
<my-component ref="my"></my-component>
Vue.component('my-component', {
data: function () {
return {name: "66"};
},
template: '<div>66</div>'
});
var app = new Vue({
el: "#app"
});
//通过实例属性$refs访问子组件
app.$refs.my.name;
异步组件
将应用拆分多个小模块,按需从服务器下载,Vue允许将组件定义为一个工厂函数,动态解析,建议配合Webpack异步加载功能食用
<async-component></async-component>
Vue.component('async-component', function (resolve, reject) {
//模拟异步网络请求
setTimeout(function () {
//调用porime模式resolve方法表示成功返回异步组件
resolve({template: '<div>async</div>'});
}, 1000);
});
var app = new Vue({
el: "#app"
});
组件命名约定
注册组件(或props)使用kebab-case,camelCase,PascalCase,HTML模板中只能使用kebab-case
递归组件
设置了name选项,组件才可以递归调用自己
<my-component :tree="tree"></my-component>
Vue.component('my-component', {
props: ['tree'],
name: 'component',
template: '<div>' +
'<div v-if="tree instanceof Array">' +
'<component :tree="item" v-for="item in tree"></component>' +
'</div>' +
'<div v-else>{{tree}}</div>' +
'</div>'
});
var app = new Vue({
el: "#app",
data: {
tree: [[1, 2], 3]
}
});
要保证递归调用有终止条件(如v-if最终返回false),否则可能出现死循环
组件循环引用
A引用B,B中也引用A,使用Vue.component注册的全局组件,框架会自动解决依赖组件的矛盾
X-Templates
<my-component></my-component>
<script type="text/x-template" id="my-component">
<div>hello</div>
</script>
Vue.component('my-component', {
template: '#my-component'
});
低开销静态组件使用v-once,缓存渲染结果
Vue.component('my-component', {
template: '<div v-once>message</div>'
});