何时使用 is
当使用DOM元素作为模板(即为el指定某个元素)时,会受到HTML的一些限制:
1. Vue只有在浏览器解析和标准化HTML后才能获取模板内容。
2. 某些标签元素限制了其所能包含的内容,比如 <ul>
, <table>
, select
等,如下渲染可能出错
<select>
<my-component></my-component>
</seelct>
select
元素中规定只有 option
元素,可能会被认为是无效的内容,辗转的方式如下:
<select>
<option is="my-component"></option>
</seelct>
不过,如果使用的是字符串模板,那么这些限制将不存在。
动态组件
上文提到的 is
属性,可令某标签变成一个组件。那么切换 is
属性也可以更换组件实例。
<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! -->
</component>
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
既然是在组件实例之间切换,如果频繁的销毁与创建性能必定收到影响, keep-alive
属性会缓存不活动的组件实例,而不是销毁他们。
使用方式如下:
<keep-alive>
<component :is="currentView">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
模板的data要求必须是函数
- 如果不是函数,会发出警告
- 之所以这么要求,是因为若data是一个对象,那么多个模板实例将共享同一个对象,造成逻辑上的错误
父–>子组件协同工作 — Props传递
使用props,从父组件传递数据 —> 至子组件
- 传递静态数据
父组件使用一个自定义属性传递数据。子组件则在组件内部用props选项声明该自定义属性。
<component message="from parent"></component>
Vue.component('component', {
template: '<div>{{ message }}<div>',
props: ['message']
})
可见,message属性就像普通的属性一样被赋值,组件内部获取该值从而更新视图。
- 传递动态数据
既然组件自定义的属性与普通的html属性类似,那么也可以将子组件的自定义属性绑定至父组件的某个属性,从而实现该自定义属性的动态变化。
<input type="text" v-model="parentAttr">
<!-- html中的属性名与js中定义的不同,下文会提到 -->
<component :message-addd="parentAttr"></component>
Vue.component('component', {
template: '<div>{{ messageAddd }}<div>',
props: ['messageAddd']
})
new Vue({
el: '#app',
data: {
'parentAttr': 'i am pa'
}
})
每当父组件的属性发生变化,子组件的视图也会更新。
父–>子组件协同工作 — Props注意点
- HTML特性不区分大小写,当使用非字符串模版时(如果使用字符串模版,不用在意这些限制),prop的名字形式会从 camelCase 转为 kebab-case(短横线隔开)
这也是为什么上面的例子中js中的messageAdd,在html中却是message-add的原因 - 如果我希望传递给子组件一个number类型的数字,该怎么传递呢?
<!--这里只传了一个字符串"1"-->
<comp some-prop="1"></comp>
<!--这里则是传了整型1-->
<comp :some-prop="1"></comp>
- prop是单项绑定的,当父组件属性变化时,传给子组件;但反过来不会发生。prop属性一般有两种情况:
- 作为初始值传入,只用作初始值。
- 定义一个computed属性,依赖prop属性
// 情况1, 初始值
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
// 情况2, 计算属性依赖
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
父–>子组件协同工作 — Props验证
在给组件的prop传递值时,往往需要一些验证,以满足逻辑需求。一般都是 属性名:属性验证 的格式
// type可以是:String,Number,Boolean,Function,Object,Array
props:{
// 整型
propA: Number,
// 多种类型(并不是说要传列表)
propB: [String, Number],
// 必传且是字符串
propC: {
type: Boolean,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回,依然是多个实例可能共享同一个对象的问题
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
当 prop 验证失败了, Vue 将拒绝在子组件上设置此值
子–>父组件协同工作 — 自定义事件
之前提到,子组件是不被允许改变prop的值的,如果子组件要把数据传递回去,那就是自定义事件。
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
一般流程为:
- 子组件监听某自定义事件
- 父组件在使用子组件的地方监听子组件的自定义事件
- 子组件触发自定义事件
- 父组件监听的子组件的自定义事件也被触发
示例
点击任何一个子组件,该子组件内部计数器会增加,同时父组件计数器都会增加
<!--html 部分-->
<div id="app-6">
{{ total }}
<div>
<component-6 @increament="increament"></component-6>
<component-6 @increament="increament"></component-6>
</div>
</div>
//js 部分
Vue.component('component-6', {
template: '<button @click="partClick">{{ partTotal }}</button>',
data: function () {
return {
partTotal: 0
}
},
methods: {
partClick: function () {
this.partTotal += 1;
this.$emit('increament')
}
}
})
var app6 = new Vue({
el: '#app-6',
data: {
total: 0
},
methods: {
increament: function () {
this.total += 1
}
}
})
除了自定义事件,如果想要绑定原生事件的话,那么可以如下使用:
<my-component v-on:click.native="doTheThing"></my-component>
子–>父组件协同工作 — 表单控件的 v-model
之前了解到:v-modle
= v-bind:value
+ v-on:input
那么,父组件如和监听子组件的表单控件的内容呢?想法肯定是利用子组件的事件将数据向上传递.
至于为什么v-model可以放在此,看官网介绍
<div class="part">
<p class="title">子组件的表单控件值传递到父组件</p>
<div id="app-7">
<p>子组件的消息:{{ message }}</p>
<!-- 相当于子组件的value属性绑定了父组件的message, 所以子组件的value属性会跟着父组件的message变化 -->
<!-- 也相当于父组件监听了子组件的input事件, 子组件只需要emit input事件,且带上一个value参数即可-->
<component-7 v-model="message" :parent-info="info"></component-7>
</div>
</div>
Vue.component('component-7', {
props: ['value', 'parentInfo'],
data: function () {
return {
inputId: 'input' + Math.random(),
}
},
methods: {
onput: function (event) {
this.$emit('input', event.target.value)
}
},
// 在子组件的input内容发生变化后,触发input事件,从而触发onput事件
// onput事件将子组件的input的值传递到父组件,父组件获取到该值从而给message赋值
// message值发生变化,将其变动传递到子组件中,从而引发子组件中内容的变化
template: '\
<div>\
<p> {{ parentInfo }} </p>\
<input :id="inputId" v-on:input="onput">\
<p>{{ value }}</p>\
</div>\
'
})
var app7 = new Vue({
el: '#app-7',
data: {
message: 'init of parent',
info: "parent's info"
}
})
父组件使用Slots为子组件分发内容
- 主要用于混合父组件的内容与子组件自己的模板, 使用了
<slot>
元素作为原始内容的插槽。 - 关于slot, 除非子组件模板包含至少一个
<slot>
插口,否则父组件的内容将被丢弃。 - 当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身
- 最初在
<slot>
标签中的任何内容都被视为备用内容,在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。 - 如果子组件有多个slot, 那么可以为每个slot指定name属性作为标识,父组件通过声明name属性来指定内容所属slot。 另外,可以有一个没有name属性的slot, 作为父组件中没有声明name属性的内容的去处。