使用组件的细节点
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
<script>
Vue.component('row', {
template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
我们发现 tr 不在 tbody 里面,原因是 HTML5 规定 table 下面必须是 tbody,tbody 下面必须是 tr。
所以我们需要在 tbody 下面写 tr,但是我们可以通过 is
属性来指明模版。
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
<script>
Vue.component('row', {
template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component('row', {
data() {
return {
content: 'This is a row'
}
},
template: '<tr><td>{{content}}</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
在子组件中定义 data 时,data 必须是一个函数,不能是一个对象。
ref
引用
我希望 div 被点击的时候,把 div 的内容打印出来。
<div id="app">
<div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
console.log(this.$refs.hello.innerHTML);
}
}
})
</script>
$refs
指所有引用,.hello
就是 ref=“hello” 这个引用。
$ref 在组件中的使用:
「 计数器 」
<div id="app">
<counter></counter>
</div>
<script>
Vue.component('counter', {
data() {
return {
number: 0
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
}
}
})
var vm = new Vue({
el: '#app'
})
</script>
「 counter 求和 」
<body>
<div id="app">
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<div>total: {{total}}</div>
</div>
<script>
Vue.component('counter', {
data() {
return {
number: 0
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
this.$emit('change');
}
}
})
var vm = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleChange() {
this.total = this.$refs.one.number + this.$refs.two.number;
}
}
})
</script>
</body>
父子组件的数据传递
父组件通过属性的形式向子组件传递数据。
子组件通过 props:[]
接收数据。
<div id="app">
<counter :count="0"></counter> <!-- 通过:count属性传值 -->
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ['count'],
template: '<div>{{count}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
counter: counter
}
})
</script>
Vue 单向数据流
父组件可以向子组件通过属性的形式传递参数,传递的数据可以随便进行修改。
但是子组件只能使用父组件传递过来的数据,不能修改父组件传递过来的数据。
一个父组件可以传值给多个子组件。一旦子组件接收的是引用形式的数据时,在子组件中改变了数据内容,会影响其它的子组件。
子组件使用父组件传递过来的数据时,不能修改。这个时候可以复制一份:(在 data 里定义一个 number,把父组件的值赋值给 number)
<div id="app">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ['count'],
data() {
return {
number: this.count
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
}
}
}
var vm = new Vue({
el: '#app',
components: {
counter: counter
}
})
</script>
子组件通过事件的形式向父组件传值。子组件被点击的时候,可以通过 this.$emit('change')
触发一个 change 事件。
<div id="app">
<counter :count="0" @change="handleIncrease"></counter>
<!-- @监听事件,名字可以随便写(比如可以写成 @inc),和下面 this.$emit 里的对应即可。 -->
<counter :count="1" @change="handleIncrease"></counter>
<div>total: {{total}}</div>
</div>
<script>
var counter = {
props: ['count'],
data() {
return {
number: this.count
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
this.$emit('change', 1);
}
}
}
var vm = new Vue({
el: '#app',
data: {
total: 1
},
components: {
counter: counter
},
methods: {
handleIncrease(step) {
this.total += step;
}
}
})
</script>
this.$emit('change', 1);
的第二个参数实际上和上面那句this.number++;
相对应,代表增长的步长。比如,上面写成this.number=this.number+2;
则可以写成this.$emit('change', 2);
用以告诉父组件增加了多少。
组件参数校验与非 props 特性
组件参数校验
我们都知道父组件通过属性(content="hello world"
)向子组件传递参数,那组件的参数校验是什么?
父组件向子组件传递了一些内容,子组件有权对这些内容做一个约束。这些约束就可以叫做参数的校验。
比如:需求是传递过来的 content 必须是一个字符串。
<div id="app">
<child :content="{}"></child> <!-- :content 有:时,后面跟的是js变量,没有:时,后面的值是字符串-->
</div>
<script>
Vue.component('child', {
props: {
content: String
},
template: '<div>{{content}}</div>'
})
var vm = new Vue({
el: '#app',
})
</script>
如果 content 不是一个字符串(现在是{},一个对象),控制台就会报错。
参数校验可以是一个数组(有多个条件):
content: [Number, String]
也可以是一个对象:
<child content="hello"></child>
content: {
type: String, // 类型
required: true, // 是否必须存在
default: 'default value' // 默认值
validator(value) { // 自定义校验器
return (value.length > 5)
}
}
非 props 特性
props 特性指:当父组件使用子组件时,通过属性向子组件传值的时候,恰好子组件中声明了对父组件传递过来的属性的接收(props: )。父子组件有一个对应关系。
父组件传,子组件接。
非 props 特性,子组件中没有声明。没有接收父组件传的值。没办法使用。
父组件传,子组件不接。
声明了一个非 props 特性的时候,其属性会展示在子组件最外层的 dom 标签的 html 属性里面。
给组件绑定原生事件
<div id="app">
<child @click="handleClick"></child> <!-- 这里绑定的事件是监听的自定义事件(目前无效)-->
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>', // 这里绑定的事件是监听的原生事件
methods: {
handleChildClick() {
console.log("handleChildClick");
}
}
})
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
this.$emit('click')
console.log("handleClick"); // (目前无效)
}
}
})
</script>
监听的自定义事件如何被触发?子组件想触发父组件的事件的监听,需要调用 this.$emit()
方法。
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>',
methods: {
handleChildClick() {
this.$emit('click');
// console.log("handleChildClick");
}
}
})
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
console.log("handleClick");
}
}
})
</script>
还有什么更简单的方法吗?
<child @click.native="handleClick"></child>
可以在事件绑定的后面加上 .native
修饰符,监听原生事件。
非父子组件间的传值
利用总线机制解决非父子组件间的传值问题。
( Bus / 总线 / 发布订阅模式 / 观察者模式 )
比如:我想要点击上面的 div 时,把其内容改成 daze;点击下面的 div 时,把其内容改成 bule。
<div id="app">
<child content="bule"></child>
<child content="daze"></child>
</div>
<script>
Vue.prototype.bus = new Vue() //bus总线
Vue.component('child', {
data() {
return {
selfContent: this.content
}
},
props: {
content: String
},
template: '<div @click="handleClick">{{selfContent}}</div>',
methods: {
handleClick() {
this.bus.$emit('change', this.selfContent);
}
},
mounted() {
var this_ = this;
this.bus.$on('change', function (msg) {
this_.selfContent = msg;
})
}
})
var vm = new Vue({
el: '#app'
})
</script>
在 Vue 中使用插槽
<div id="app">
<child content='<p>bule_daze</p>'></child>
</div>
<script>
Vue.component('child', {
props: ['content'],
template: `<div>
<p>hello</p>
<div v-html="this.content">{{content}}</div>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
如果我不想要 p 标签外面显示 div 标签,应该如何去做?
template 占位符在这里是不好用的。
而通过 content 传值,想直接使用 content 里面的 p 标签是有问题的,必须在外面包裹一个 div (<div v-html="this.content">
);且当传递的内容很多时,代码不利于阅读。
当子组件有一部分内容是根据父组件传递过来的 DOM 进行显示时,我们可以利用插槽 slot。
<div id="app">
<child>
<p>bule_daze</p>
</child>
</div>
<script>
Vue.component('child', {
props: ['content'],
template: `<div>
<p>hello</p>
<slot></slot>
</div>`,
})
var vm = new Vue({
el: '#app'
})
</script>
默认值
slot 插槽还可以定义默认值:<slot>默认内容</slot>
(当父组件使用子组件时不传递插槽的内容时,才会显示出默认内容。<child></child>
)
具名插槽
如果只写成 <slot></slot>
,代表插槽中的所有内容。如果我希望 slot 的内容分别对应,可以利用具名插槽,即给插槽取个名字。
- 父组件中
slot="header"
- 子组件中
<slot name="header"></slot>
<div id="app">
<my-content>
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</my-content>
</div>
<script>
Vue.component('my-content', {
template: `<div>
<slot name="header"></slot>
<div class="content">content</div>
<slot name="footer"></slot>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
具名插槽也可设置默认值。
作用域插槽
<div id="app">
<child></child>
</div>
<script>
Vue.component('child', {
data() {
return {
list: [1, 2, 3, 4]
}
},
template: `<div>
<ul>
<li v-for="item of list">
{{item}}
</li>
</ul>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
现在有一个需求,child 组件有可能在很多的地方被调用。我们希望在不同的地方调用 child 组件的时候,列表怎样循环列表的样式不是由 child 组件控制的,而是外部告诉我们的。(即 template 里面需要把 li 去掉)
需要用到作用域插槽:
<div id="app">
<child>
<template slot-scope="props">
<li>{{props.item}}-hello</li>
</template>
</child>
</div>
<script>
Vue.component('child', {
data() {
return {
list: [1, 2, 3, 4]
}
},
template: `<div>
<ul>
<slot v-for="item of list" :item=item ></slot>
</ul>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
父组件调用子组件的时候,给子组件传了一个插槽,这个插槽叫做作用域插槽。作用域插槽必须是一个以 <template></template>
开头和结尾的内容;且这个插槽要声明其要接收的数据都放在哪(slot-scope="props"
都放在 props 里面);还需要告诉子组件一个模版的信息,接到 props 应该如何展示(<li>{{props.item}}-hello</li>
)。
使用情境
当子组件做循环时,或者某一部分的 DOM 结构应该由外部传递进来的时候,使用作用域插槽。
使用作用域插槽时,子组件可以向父组件的插槽里面传数据。父组件如果想要接收这个数据,必须在外层使用 template,同时通过 slot-scope 对应的属性的名字来接收传递过来的所有的数据。
动态组件与 v-once 指令
<div id="app">
<child-one></child-one>
<child-two></child-two>
<button>change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app'
})
</script>
现在想实现当点击 change 按钮的时候,一会 child-one 进行显示,一会 child-two 进行显示:
<div id="app">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
type: 'child-one'
},
methods: {
handleBtnClick() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
动态组件
可以利用动态组件的方式来实现上述需求。
<div id="app">
<component :is="type"></component>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
type: 'child-one'
},
methods: {
handleBtnClick() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
<component></component>
是 Vue 里面自带的一个标签,指的就是动态组件。动态组件的意思就是根据属性is
里面数据的变化自动加载不同的组件。
v-once
上面的动态组件在每次点击按钮切换的时候,Vue 底层会帮我们进行判断,第一个组件不用了,就会销毁第一个组件,创建第二个组件;当第二个组件不用了的时候,就会销毁第二个组件,创建第一个组件。
这种操作会耗费一定的性能。如果组件的内容每一次都一样,我们可以使用 v-once
指令:
Vue.component('child-one', {
template: '<div v-once>child-one</div>'
})
Vue.component('child-two', {
template: '<div v-once>child-two</div>'
})
第一次被渲染的时候,会保存至内存中;当再次使用的时候不需要重新创建,而是直接从内存中拿。
v-once 可以提高静态内容的展示效率。