您的位置:首页 > 产品设计 > UI/UE

Vue.js 学习(9) -- 组件*1*

2017-05-24 16:51 633 查看
终于等到组件。

组件是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