Vue.js 学习(9) -- 组件*1*
2017-05-24 16:51
633 查看
终于等到组件。
组件是Vue最强大的功能之一,它可以提供一层封装,将一些自定义的功能打包,以供开发者在后续的代码中使用。
全局组件
PS:对于组件的标签名(在上面这个栗子中是my-component),Vue并不强制要求遵守W3C规则(小写,分隔符为一个短杠),但是为了代码统一,还是建议大家都遵守这个规则。
组件注册完成后,就可以在代码中这样写:
但是要注意的是:要确保在初始化example实例之前完成注册组件的代码:
代码运行后,会渲染为:
局部组件
可以在新建vue实例的时候,为传入的参数添加一个components属性,就可以完成局部组件的注册:
一些必要的解释
第一:
当使用 DOM 作为模版时(例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素 < ul > , < ol >, < table > , < select > 限制了能被它包裹的元素, < option > 只能出现在其它元素内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
此时,自定义组件 < my-row > 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:
第二:data 必须是函数
这点优点奇怪,但是,在构建组件的过程中,data属性必须是函数。
然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
定义:
引用:
进阶:动态Prop
动态prop是利用指令v-bind实现的——还记得v-bind嘛?它可以将一个动态的变量绑定为html元素的属性。
还是上一个栗子中的child
这个栗子中,v-bind将child标签的message属性值绑定为parentMsg,而parentMsg是通过v-model指令和input的value绑定。这样就达成效果:input内输入的值实时称为child的message属性,并显示出来。
单向数据流
prop只能由父组件传递给子组件(子组件不能传递给父组件prop),并且在子组件中,只能接收prop值并作出相应的反应,但不可以修改prop。这一点和React的props很相似。
prop验证
当我们对prop类型值无要求时,这样定义props:
而如果,我们希望props是某种特性类型的变量,就要以如下方式定义:
其中,type可以是如下类型:
当 prop 验证失败了,如果使用的是开发版本会抛出一条警告。
所有的Vue实例都有事件接口,开发者可以使用:
先给出一个栗子,然后我们在栗子后解释代码:
代码分析:
1、使用Vue.component方法创建自定义的组件button-counter。该组件拥有自己的数据:counter,还有方法increment。在template的定义中,我们可以看到,使用v-on绑定了事件,当按钮被点击时触发increment方法。而该方法被触发后,执行方法的最后,它触发了increment事件。这个increment事件将会被传递到上层。
2、
以这种事件传递的方式,子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。
在组件中使用v-model
首先要明确,指令v-model其实只是一个语法糖。
相当于
也就是,当input事件被触发,something就会随着输入改变,从而改变了input的value;同时,something作为一个变量,可以在代码中修改或者饮用,它可以和value一直保持同步。
在组件中使用v-model:
对比前面的语法糖,并假想这里的v-model替换为v-bind&v-on:input…
我们就可以明白,如果想让组件的v-model生效,组件必须:
1、接受一个value属性;
2、在有新的value时触发input事件。
一个更复杂但是完整的应用可见:
https://jsfiddle.net/chrisvfritz/1oqjojjx/?utm_source=website&utm_medium=embed&utm_campaign=1oqjojjx
总的来说,就是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
如下这段代码无效:
正确的写法是:
slot
slot到底是干什么的呢?
来看这段代码:
要注意的是,app这个组件不知道它的字节点有什么,app的父组件才知道。
这样,如果app组件中没有slot,那么app下的app-header/app-footer就会被丢弃。
而如果,app组件的模版中有slot,那么,app-header/app-footer就会插入到slot所在的位置,并替换掉slot.
另一个栗子:
假定 my-component 组件有下面模板:
父组件模版
结果:
具名slot
多个slot的时候,我们可以使用属性name来规定如何将父组件的内容嵌入到子组件模版中。
例如,假定我们有一个 app-layout 组件,它的模板为:
父组件:
结果:
我们注意到,在app-layout组件中,仍有一个匿名 slot ,它是默认 slot ,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot ,这些找不到匹配的内容片段将被抛弃。
作用域插槽
作用域插槽是一种特殊类型的插槽,用作(可以传入数据的)可重用模板,而不是已渲染元素。
栗子:
子组件:
父组件:
讲解:具有特殊属性 scope 的 < template > 元素,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 prop 对象。这样,我们可以完成父组件和子组件之间的数据交流。
渲染结果:
组件是Vue最强大的功能之一,它可以提供一层封装,将一些自定义的功能打包,以供开发者在后续的代码中使用。
使用组件的基本格式
组件可以在全局范围内注册,也可以仅在另一个实例或组件中注册,下面我们先来介绍在全局范围内注册组件:全局组件
Vue.component('my-component', { // 选项 })
PS:对于组件的标签名(在上面这个栗子中是my-component),Vue并不强制要求遵守W3C规则(小写,分隔符为一个短杠),但是为了代码统一,还是建议大家都遵守这个规则。
组件注册完成后,就可以在代码中这样写:
<div id="example"> <my-component></my-component> </div>
但是要注意的是:要确保在初始化example实例之前完成注册组件的代码:
// 注册 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 创建根实例 new Vue({ el: '#example' })
代码运行后,会渲染为:
<div id="example"> <div>A custom component!</div> </div>
局部组件
可以在新建vue实例的时候,为传入的参数添加一个components属性,就可以完成局部组件的注册:
new Vue({ // ... components: { // <my-component> 将只在父模板可用 'my-component': { template: '<div>A custom component!</div>' } } })
一些必要的解释
第一:
当使用 DOM 作为模版时(例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素 < ul > , < ol >, < table > , < select > 限制了能被它包裹的元素, < option > 只能出现在其它元素内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table> <my-row>...</my-row> </table>
此时,自定义组件 < my-row > 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:
<table> <tr is="my-row"></tr> </table>
第二:data 必须是函数
这点优点奇怪,但是,在构建组件的过程中,data属性必须是函数。
var data = { counter: 0 } Vue.component('simple-counter', { template: '<button v-on:click="counter += 1">{{ counter }}</button>', // data 是一个函数,否则 Vue 会发出警告 data: function () { return { counter: 0 } } }) new Vue({ el: '#example-2' })
父组件和子组件
父子组件通常是这样的关系:父组件在他的templete中使用了子组件。此时他们两个一定需要互相传递信息:在Vue中,父子组件传递信息可以总结为props down, events up——父组件给子组件传递数据,子组件给父组件传递它的内部事件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
Prop
基础:使用prop传递数据定义:
Vue.component('child', { // 声明 props props: ['message'], // 就像 data 一样,prop 可以用在模板内 // 同样也可以在 vm 实例中像 “this.message” 这样使用 template: '<span>{{ message }}</span>' })
引用:
<child message="hello!"></child>
进阶:动态Prop
动态prop是利用指令v-bind实现的——还记得v-bind嘛?它可以将一个动态的变量绑定为html元素的属性。
还是上一个栗子中的child
<div> <input v-model="parentMsg"> <br> <child v-bind:message="parentMsg"></child> </div>
这个栗子中,v-bind将child标签的message属性值绑定为parentMsg,而parentMsg是通过v-model指令和input的value绑定。这样就达成效果:input内输入的值实时称为child的message属性,并显示出来。
单向数据流
prop只能由父组件传递给子组件(子组件不能传递给父组件prop),并且在子组件中,只能接收prop值并作出相应的反应,但不可以修改prop。这一点和React的props很相似。
prop验证
当我们对prop类型值无要求时,这样定义props:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
而如果,我们希望props是某种特性类型的变量,就要以如下方式定义:
Vue.component('example', { 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 value > 10 } } } })
其中,type可以是如下类型:
String Number Boolean Function Object Array
当 prop 验证失败了,如果使用的是开发版本会抛出一条警告。
事件
上面讨论的prop是父组件传递给子组件信息的方式。如果子组件想要传递给父组件信息应该怎么做呢?答案就是事件。所有的Vue实例都有事件接口,开发者可以使用:
$on(eventName)监听事件;使用
$emit(eventName)触发事件。
先给出一个栗子,然后我们在栗子后解释代码:
<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>
Vue.component('button-counter', { template: '<button v-on:click="increment">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { increment: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })
代码分析:
1、使用Vue.component方法创建自定义的组件button-counter。该组件拥有自己的数据:counter,还有方法increment。在template的定义中,我们可以看到,使用v-on绑定了事件,当按钮被点击时触发increment方法。而该方法被触发后,执行方法的最后,它触发了increment事件。这个increment事件将会被传递到上层。
2、
<button-counter v-on:increment="incrementTotal"></button-counter>这段代码中可以看到,当触发了increment事件的时候,就会执行父组件 的incrementTotal方法(在new Vue…中我们可以找到这个方法)。
以这种事件传递的方式,子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。
在组件中使用v-model
首先要明确,指令v-model其实只是一个语法糖。
<input v-model="something">
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
也就是,当input事件被触发,something就会随着输入改变,从而改变了input的value;同时,something作为一个变量,可以在代码中修改或者饮用,它可以和value一直保持同步。
在组件中使用v-model:
<currency-input v-model="price"></currency-input>
对比前面的语法糖,并假想这里的v-model替换为v-bind&v-on:input…
我们就可以明白,如果想让组件的v-model生效,组件必须:
1、接受一个value属性;
2、在有新的value时触发input事件。
Vue.component('currency-input', { template: `\ <span>\ $\ <input\ ref="input"\ v-bind:value="value"\ v-on:input="updateValue($event.target.value)">\ </span>\ `, props: ['value'], methods: { // 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制 updateValue: function (value) { var formattedValue = value // 删除两侧的空格符 .trim() // 保留 2 小数位 .slice(0, value.indexOf('.') + 3) // 如果值不统一,手动覆盖以保持一致 if (formattedValue !== value) { this.$refs.input.value = formattedValue } // 通过 input 事件发出数值 this.$emit('input', Number(formattedValue)) } } })
一个更复杂但是完整的应用可见:
https://jsfiddle.net/chrisvfritz/1oqjojjx/?utm_source=website&utm_medium=embed&utm_campaign=1oqjojjx
slot
编译作用域总的来说,就是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
如下这段代码无效:
<!-- 无效,处于someChildProperty处的变量只会在父组件作用域查找 --> <child-component v-show="someChildProperty"></child-component>
正确的写法是:
Vue.component('child-component', { // 有效,因为是在正确的作用域内 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
slot
slot到底是干什么的呢?
来看这段代码:
<app> <app-header></app-header> <app-footer></app-footer> </app>
要注意的是,app这个组件不知道它的字节点有什么,app的父组件才知道。
这样,如果app组件中没有slot,那么app下的app-header/app-footer就会被丢弃。
而如果,app组件的模版中有slot,那么,app-header/app-footer就会插入到slot所在的位置,并替换掉slot.
另一个栗子:
假定 my-component 组件有下面模板:
<div> <h2>我是子组件的标题</h2> <slot> 只有在没有要分发的内容时才会显示。 </slot> </div>
父组件模版
<div> <h1>我是父组件的标题</h1> <my-component> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </my-component> </div>
结果:
<div> <h1>我是父组件的标题</h1> <div> <h2>我是子组件的标题</h2> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </div> </div>
具名slot
多个slot的时候,我们可以使用属性name来规定如何将父组件的内容嵌入到子组件模版中。
例如,假定我们有一个 app-layout 组件,它的模板为:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
父组件:
<app-layout> <h1 slot="header">这里可能是一个页面标题</h1> <p>主要内容的一个段落。</p> <p>另一个主要段落。</p> <p slot="footer">这里有一些联系信息</p> </app-layout>
结果:
<div class="container"> <header> <h1>这里可能是一个页面标题</h1> </header> <main> <p>主要内容的一个段落。</p> <p>另一个主要段落。</p> </main> <footer> <p>这里有一些联系信息</p> </footer> </div>
我们注意到,在app-layout组件中,仍有一个匿名 slot ,它是默认 slot ,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot ,这些找不到匹配的内容片段将被抛弃。
作用域插槽
作用域插槽是一种特殊类型的插槽,用作(可以传入数据的)可重用模板,而不是已渲染元素。
栗子:
子组件:
<div class="child"> <slot text="hello from child"></slot> </div>
父组件:
<div class="parent"> <child> <template scope="props"> <span>hello from parent</span> <span>{{ props.text }}</span> </template> </child> </div>
讲解:具有特殊属性 scope 的 < template > 元素,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 prop 对象。这样,我们可以完成父组件和子组件之间的数据交流。
渲染结果:
<div class="parent"> <div class="child"> <span>hello from parent</span> <span>hello from child</span> </div> </div>
相关文章推荐
- vue.js 2.0父子组件学习入门套路
- vue.js 学习之组件之间数据传递详解
- Vue.js入门学习--组件的注册使用+开奖小游戏(八)
- Vue.js学习(四):父子组件中的生命周期与钩子函数的调用顺序
- Vue.js学习系列(八)---如何使用组件
- vue.js 2.0父子组件学习入门套路(推荐相关阅读)
- Vue.js组件学习
- Vue.js 2.0学习教程之从基础到组件详解
- vue.js重学之旅(3)——vue.js组件学习and组件间通信
- Vue.js第四天学习笔记(组件)
- vue.js学习04之组件2
- vue.js学习笔记(三)--父子组件通信总结
- Vue.js学习 Item16 – 实现一个自定义分页组件vue-paginaiton
- Vue.js入门学习--父子组件的说明和简单通信(十)
- Vue.js学习笔记:组件
- Vue.js学习系列(四十二)-- Vue.js组件
- Vue.js 学习10 Element基于Vue2.0的组件库
- Vue.js入门学习--组件的注册和使用(二)
- vue.js学习之组件(下篇)
- vue.js学习之组件(下篇)