在日常开发时,始终应该以“高内聚,低耦合”设计思想实现需求,而组件化开发也是对这个原则的具体实现。对前端来说,将页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,且不利于后续的管理以及扩展,所以开发时需要对功能进行拆分,这就是组件化开发。目前前端流行的框架也多以组件化开发思想进行开发。
在 Vue 中注册组件分成两种:1. 全局组件:在任何其他的组件中都可以使用的组件;2. 局部组件:只有在注册的组件中才能使用的组件。
注册全局组件可以用 app.component()进行注册,app.component()传入两个参数,(组件名称, 组件的对象)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <template id="item">
<!-- 组件item的模板 -->
</template>
<script src="../lib/vue.js"></script>
<script>
const App = {}
const app = Vue.createApp(App)
app.component('product-item', {
template: '#item',
})
app.mount('#app')
</script>
|
全局注册的组件本身也有自己的代码逻辑,可以使用 data、computed、methods 等属性和方法。
在通过 app.component 注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种:1. 使用 kebab-case(短横线分割符);2. 使用 PascalCase(驼峰标识符)。
注册全局组件后,即使没有用到该组件,在打包后得到的 JavaScript 代码中仍然会包含该组件的代码。所以为了性能优化,大部分时候会选择局部注册组件。
局部注册是在我们需要使用到的组件中,通过 components 属性选项来进行注册,components 选项对应的是一个对象,对象中的键值对是“组件的名称: 组件对象”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| <template id="item">
<!-- 组件item的模板 -->
</template>
<template id="nav">
<!-- 组件nav的模板 -->
</template>
<script src="../lib/vue.js"></script>
<script>
// 可以定义组件对象,然后注册组件的时候直接引用
const ProductItem = {
template: '#item',
}
const app = Vue.createApp({
components: {
ProductItem,
HomeNav: {
template: '#nav',
components: {
// 如果不在HomeNav内部注册ProductItem组件,
// 就不能在Home内部使用ProductItem组件。
ProductItem,
},
},
},
})
app.mount('#app')
</script>
|
所以在真实开发中,我们可以通过一个后缀名为.vue 的 single-file components (单文件组件) 来解决,并且可以使用 webpack 或者 vite 或者 rollup 等构建工具来对其进行处理。
在这个组件中我们可以获得非常多的特性:代码的高亮;ES6、CommonJS 的模块化能力;组件作用域的 CSS;使用预处理器来构建更加丰富的组件,比如 TypeScript、Babel、Less、Sass 等。
在单文件组件开发模式下,全局注册组件在项目文件夹 main.js 下进行注册,局部注册组件在组件内部进行注册。
插槽是指将共同的元素、内容在组件内进行封装,将不同的元素使用 slot 作为占位,让外部决定到底显示什么样的元素。
Vue 中将<slotl>元素作为承载分发内容的出口。在封装组件中,使用特殊的元素<slotl>就可以为封装组件开启一个插槽,该插槽插入什么内容取决于父组件如何使用。
1
2
3
4
5
6
7
8
9
10
11
| <!-- 子组件 -->
<div>
<slot>
<p>默认内容</p>
</slot>
</div>
<!-- 父组件 -->
<son-component>
<button>按钮</button>
</son-component>
|
有多个插槽时通过,就可以使用具名插槽给插槽起一个名字。在子组件中使用 v-slot:(#)绑定父组件对应的插槽,v-slot:[dynamicSlotName]
方式动态绑定一个名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| <!-- 子组件 -->
<div class="nav-bar">
<div class="left">
<slot name="left">left</slot>
</div>
<div class="center">
<slot name="center">center</slot>
</div>
<div class="right">
<slot name="right">right</slot>
</div>
</div>
<!-- 父组件 -->
<nav-bar>
<template #left>
<button></button>
</template>
<template #center>
<span></span>
</template>
<template v-slot:right>
<a href="#"></a>
</template>
</nav-bar>
|
父组件传递给子组件:通过 props 属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 父组件引用子组件:
<template>
<son-component pro="prop1" pro2="prop2" />
</template>
<script>
import SonComponent from './SonComponent.vue'
export default {
components: {
SonComponent,
},
}
</script>
子组件接受父组件props传值:
<script>
export default {
// 1.props数组语法 弊端: 1.不能对类型进行验证 2.没有默认值的
// 对象类型写默认值时, 需要编写default的函数, 函数返回默认值
props: ["prop1", "prop2"]
// 2.props对象语法
props: {
pro1: {
type: String,
default: "我是prop1"
},
pro2: {
type: Number,
required: true
},
}
}
</script>
|
子组件传递给父组件:通过$emit 触发事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| <!-- 子组件监听事件,并通过this.$emits()传出。this.$emit()接受两个参数,方法名以及参数。 -->
<script>
export default {
methods: {
sonmethod(args) {
this.$emit('sonmethod', args)
},
},
}
</script>
<!-- 父组件自定义方法,并监听子组件传出的传出的方法 -->
<template>
<son-component @son="parentmethod" />
</template>
<script>
import SonComponent from './SonComponent.vue'
export default {
components: {
SonComponent,
},
methods: {
parentmethod(args) {
return args
},
},
}
</script>
|
Vue 官方建议我们在组件中所有的 emit 事件都能在组件的 emits 选项中声明。emits 参数有俩种形式对象和数组,对象里面可以配置带校验 emit 事件,为 null 的时候代表不校验。校验的时候,会把 emit 事件的参数传到校验函数的参数里面。当校验函数不通过的时候,控制台会输出一个警告,但是 emit 事件会继续执行。
祖先组件与后代组件:
provide 和 inject。组件组件使用 provide 方法(options api)后,后代组件使用 inject(数组)即可接收祖先组件传来的数据。
事件总线:子组件引用引用事件总线工具后使用 event.emit 传出,父组件使用 eventBus.on 接收事件,组件销毁时,可以使用 eventBus.off 取消事件。