组件的使用主要内容包括:
- 组件定义
- 组件注册
- 组件组件间通信(传值)
目录
1. 基本示例
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="components-demo">
<!-- 使用组件 -->
<button-counter></button-counter>
</div>
<script>
// 定义新组件 ComponentA
var ComponentA = {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
}
//全局注册定义的组件,并将它取名为 button-counter
Vue.component('button-counter', ComponentA);
new Vue({ el: '#components-demo' })
</script>
})
2. 组件的定义
2.1 组件结构
完整的组件包括三个部分:
- template(必须)
template 部分主要是描述视图,可以使用 HTML 标签或自定义组件标签,CSS 等描述组件显示。 - script(可选)
与组件相关的 JS 脚本,一般包括 data,methods,props,watchs 等。 - style(可选)
可以将组件的显示样式定义统一定义在这个部分
2.2 使用 JavaScript 定义组件
var ComponentA = {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
}
2.3 使用 Vue 文件定义组件
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
<style scoped lang="stylus">
h1
margin 40px 0 0
color #42b983
</style>
3. 组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
3.1 全局注册
通过 Vue.component 全局注册
Vue.component('my-component-name', {
// ... options ...
})
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
3.2 局部注册
在组件 ComponentB 中使用 ComponentA , 在ComponentB 的 components 选项中注册想要使用的组件:
<template>
...
<ComponentA />
...
</template>
<script>
import ComponentA from "@/views/ComponentA";
export default {
components: {
'ComponentA': ComponentA // 组件名称:选项对象
},
// ...
}
</script>
4. 通过 Prop 向子组件传递数据
Prop 是你可以在组件上注册的一些自定义 attribute
。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property
。一个组件默认可以拥有任意数量
的 prop,任何值
都可以传递给任何 prop。
<blog-post v-bind:title="title"></blog-post>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
new Vue({
...
data:{
title: "hello"
}
})
2.3 定义 template
每个组件必须只有一个根元素, 你可以将模板的内容包裹在一个父元素内
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
3. 组件事件
3.1 基本使用
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="components-demo" :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
</div>
<script>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})
new Vue({
el: '#components-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1
}
})
</script>
简单说明:
- 组件定义 button 通过调用内建的
$emit
方法并传入事件名称来触发一个事件 - 组件使用时, 监听事件并使用 JS 代码修改 font size
3.2 使用事件抛出一个值
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
<blog-post v-on:enlarge-text="postFontSize += $event"></blog-post>
# 或者使用处理函数
<blog-post v-on:enlarge-text="onEnlargeText"></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
4. 使用 v-model
4.1 v-model 直接使用
<input v-model="searchText">
等价于
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
4.2 v-model 用于组件
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的 必须:
- 将其 value attribute 绑定到一个名叫 value 的
prop
上 - 在其 input 事件被触发时,将新的值通过自定义的
input 事件
抛出
写成代码之后是这样的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
现在 v-model 就应该可以在这个组件上完美地工作起来了:
<custom-input v-model="searchText"></custom-input>
5. 插槽
定义在组件模板中的 <slot></slot>
会在渲染时替换成自定义组件组件起始标签和结束标签之间的内容。
<blog-post>会替换 slot 的内容</blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
<slot><slot>
</div>
`
})
5.1 编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。简单来说,在 slot 中无法访问实例的作用域。
5.2 后备内容
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。
5.3 具名插槽
有时我们需要多个插槽, 元素有一个特殊的 attribute:name
v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API 替代方案
- 具名插槽的缩写
即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
#替换成
<template #header>
<h1>Here might be a page title</h1>
</template>
5.4 插槽 prop
允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。
<cc v-for="p in posts"
:key="p.id"
:todo="p">
</cc>
Vue.component('cc', {
props: ['todo'],
template: `
<p>
<slot v-bind:todo="todo">
{{todo}}
</slot>
</p>
`
})
6. 保持控件状态
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。我们可以用一个 <keep-alive>
元素将其动态组件包裹起来。
7. 异步组件
暂时看不懂, 待了解
8. 处理边界情况
8.1 访问元素 & 组件
8.1.1 访问根实例
对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
8.1.2 访问父级组件实例
$parent
property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
8.1.3 访问子组件实例或子元素
你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用,可以通过this.$refs.xxx
来访问这个实例, 你应该避免
在模板或计算属性中访问 $refs。
8.1.4 依赖注入
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="components-demo" :style="{ fontSize: postFontSize + 'em' }">
<cc v-for="p in posts"
:key="p.id"
:todo="p">
</cc>
</div>
<script>
Vue.component('cc', {
props: ['todo'],
//我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property
inject: ['getMap'],
template: `
<p>
<button @click="getMap">a button</button>
<slot v-bind:todo="todo">
{{todo}}
</slot>
</p>
`
})
var vue = new Vue({
el: '#components-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1,
name: 'abc'
},
methods:{
getMap: function(){ console.log('a') }
},
// provide 选项允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
getMap: this.getMap
}
}
})
</script>
相比 $parent 来说,这个用法可以让我们在任意后代组件中访问 getMap,而不需要暴露整 实例。这允许我们更好的持续研发该组件,而不需要担心我们可能会改变/移除一些子组件依赖的东西。同时这些组件之间的接口是始终明确定义的,就和 props 一样。
负面影响
- 它将你应用程序中的组件与它们当前的组织方式
耦合
起来,使重构变得更加困难。 - 所提供的 property 是非响应式的