一、组件化
在使用Vue组件化的使用之前,我们需要清楚认识“什么是组件化?”,“为什么要使用组件化?”,“使用组件化有什么好处?”三个问题。
1. 什么是组件化
什么是组件呢?从搭积木中可以得到答案,每个人都玩过积木,一个完整的作品是通过多个不同的积木块拼凑在一起形成的一个作品。我们可以将“项目”认为是这么一个作品,而其中的“组件”就是其每一个的积木块。
我们可以将网页划分为多个部分,每一个部分在这个页面都是独立的。比如导航栏、底部栏、侧边栏等等,他们或许只在这一个页面中使用,也可能在其他页面使用。我们可以将这些部分进行组合,最终拼凑出一个网页内容。
2. 为什么要使用组件化
组件化在最初构建时的优势并不明显,甚至与统一页面编写相比需要更多的代码量,因为将某一部分内容抽离成一个组件便说明该内容存在重新复用的可能性,那么对其的编写便需要考虑到可配置性、多复用性等问题。
在Vue中的组件(Components)是具备“可配置”的特性的,我们可以通过“props、v-bind”组合传递不同的配置项以展现组件的不同内容。
在结合了组件复用之后,使用组件化的好处就十分明显了,当后期需要对这一部分内容修改时,只需要修改目标组件便可以影响全局,而不需要对多个页面的重复内容同时修改,也避免了漏改、错改、修改不统一等问题。
同时将页面拆分后各个组件之间应该是相互独立的,存在的联系也应该通过共同的父组件进行维系,而不能组件间互相操作。
在Vue中使用组件化之后也将各个部分的结构、样式、逻辑进行了抽离,可以有效的管理每一个部分的样式、JavaScript逻辑、该组件的结构,使之更为清晰,更具调理。
3. 使用组件化的好处
- 提高代码复用性:通过将页面拆分成独立的组件,可以使得每个组件都具有相对独立的功能和特性,从而可以在多个页面中重复使用,提高代码复用性,减少代码冗余。
- 提高可维护性:组件化可以使得代码结构更加清晰、模块化,易于维护和修改。每个组件都具有明确的职责和作用,不同的组件之间互相独立,修改某一个组件不会对其他组件产生影响。
- 提高可测试性:组件化可以使得每个组件都可以独立进行测试,单独验证其功能和性能,而不必依赖于整个页面。这可以使得测试更加精确和高效。
二、Vue中组件化的使用
在了解组件化三个问题之后,让我们走进Vue组件化的使用,更加深入的了解在Vue中应该如何注册/使用组件吧。接下来我们将使用Vue的组件化实现一个简单的顶部导航栏,这个导航栏他首先具有一个网站的LOGO、一个导航列表、一个用户的登录注册区域、以及一个搜索栏。
若将各个部分都看作一个组件,那么就是由LOGO、导航、登录注册、搜索这四个组件将组成一个顶部的导航栏,而导航栏又作为页面中的一个组件。这儿便形成了组件与组件之间的嵌套/包含关系。以及设置一些基础的样式,具体的实现结果见下图所示。
我们都知道Vue的组件可以通过两种方式进行编写、引入,一种在项目开发中是比较常见的单文件开发,也就是通过.vue
文件中的三大标签template、script、style对组件进行编写,还有一种方式则是在学习初期使用到的对象形式的组件定义,上述的效果便是通过对象定义的方式,具体的代码如下(具体代码见文末的git链接):
import './css/global.css';
const App = {
template: `
<div class="header">
<div class="hd-container clearfix">
<div class="logo">logo</div>
<div class="nav">
<ul class="nav-list">
<li class="nav-item">
<a href="#">首页</a>
</li>
<li class="nav-item">
<a href="#">关于我们</a>
</li>
<li class="nav-item">
<a href="#">产品中心</a>
</li>
<li class="nav-item">
<a href="#">新闻中心</a>
</li>
<li class="nav-item">
<a href="#">联系我们</a>
</li>
</ul>
</div>
<div class="search">
<input type="text" placeholder="请输入关键字" />
<button class="suspended-rollup">搜索</button>
</div>
<div class="user-box">
<div class="btn-box">
<button class="suspended-rollup">登录</button>
<button class="suspended-rollup">注册</button>
</div>
</div>
</div>
</div>
`
}
Vue.createApp(App).mount('#app');
1. 组件拆分
上述便是直接完成所有的组件部分,接着我们需要将Header组件拆分为多个子组件:LOGO组件、NAV组件、NAV-ITEM子组件、SEARCH组件、USER-BOX组件。大部分的组件拆分其实只需要将对应的代码拆离即可,如下代码所示:
const Logo = {
template: `
<div class="logo">logo</div>
`
}
const NavItem = {
props: ['navLink', 'navTitle'],
template: `
<li class="nav-item">
<a :href="navLink">{{ navTitle }}</a>
</li>
`
}
const Nav = {
props: ['navList'],
components: {
NavItem
},
template: `
<div class="nav">
<nav-item
v-for="nav of navList"
:key="nav.id"
:nav-link="nav.navLink"
:nav-title="nav.navTitle"
></nav-item>
</div>
`
}
const Search = {
template: `
<div class="search">
<input type="text" placeholder="请输入关键字" />
<button class="suspended-rollup">搜索</button>
</div>
`
}
const UserBox = {
template: `
<div class="user-box">
<div class="btn-box">
<button class="suspended-rollup">登录</button>
<button class="suspended-rollup">注册</button>
</div>
</div>
`
}
const Header = {
components: {
Logo,
Nav,
Search,
UserBox
},
data() {
return{
navList: [
{
id: 1,
navLink: '#',
navTitle: '首页'
},
{
id: 2,
navLink: '#',
navTitle: '关于我们'
},
{
id: 3,
navLink: '#',
navTitle: '产品中心'
},
{
id: 4,
navLink: '#',
navTitle: '新闻中心'
},
{
id: 4,
navLink: '#',
navTitle: '联系我们'
},
]
}
},
template: `
<div class="header">
<div class="hd-container clearfix">
<Logo />
<Nav :nav-list="navList"/>
<Search />
<UserBox />
</div>
</div>
`
}
当然我们同样可以将每一个组件对象作为一个component文件拆分出去,以LOGO组件为例,在src目录下创建logo.js
文件并使用ES6模块化进行导出:
export default Logo = {
template: `
<div class="logo">logo</div>
`
}
// main.js
import Logo from './components/Logo';
// ... Component Register
2. 单文件实现组件化
上文我们已经通过组件对象实现了组件化,而在真实的项目开发中,组件更多是通过.vue单文件的形式进行组织,实现的方式也并不复杂,只需要将对应的内容放置到对应的标签中即可。同样以Header组件为例:
<template>
<div class="header">
<div class="hd-container clearfix">
<Logo />
<Nav :nav-list="navList" />
<Search />
<UserBox />
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import Logo from './Logo/index.vue';
import Nav from './Nav/index.vue';
import Search from '../Search/index.vue';
import UserBox from './UserBox/index.vue';
const navList = reactive([
{
id: 1,
navLink: '#',
navTitle: '首页'
},
{
id: 2,
navLink: '#',
navTitle: '关于我们'
},
{
id: 3,
navLink: '#',
navTitle: '产品中心'
},
{
id: 4,
navLink: '#',
navTitle: '新闻中心'
},
{
id: 4,
navLink: '#',
navTitle: '联系我们'
},
]);
</script>
<style lang="scss" scoped>
.header {
height: 50px;
min-width: 1400px;
background-color: #ccc;
}
.hd-container {
width: 1200px;
margin: 0 auto;
}
</style>
三、组件化设计
在上文中其实我们已经完成了组件化设计,对于组件如何设计,如何拆分是一个值得思考的问题。如果拆分的太过于细致,会导致组件后期的维护性变差。我们都知道Vue组件的数据流向是单向数据流,由父组件单项传递给子组件。当组件的深度过深时,难以判断数据是由哪一层的父组件传递至子组件的,也就是难以追溯数据源。
因此在组件化设计时,我们应当遵循尽量扁平化的原则,不需要将页面中的每一个部分都拆分为组件。从更好维护的设想尽可能扁平化拆分组件,这样设计的组件独立性、配置性与可维护性高,并且组件之间相互依赖的复杂度也会得到降低。
四、全局组件注册
上文中的Search搜索组件可能并不止在Header组件中使用,也会在其他的组件中使用。对于这种高复用性的组件,我们一般不会采用按需使用的方式进行注册,而是会采用全局注册的方式进行注册,注册的方式也十分简单,只需要在main.js主文件中通过Vue的应用实例的component方法进行注册。
当然,为了使得代码的整洁且功能单一性,我们可以将全局注册的代码通过横向切割划分到一个专门注册组件的文件中,并正主文件中通过执行方法进行注册,如下:
import Search from "../components/Search";
export function registerComponent(instance) {
instance.component('Search', Search);
}
// main.js
const app = createApp(Header);
registerComponent(app);
app.mount('#app')
五、小结
本文主要从组件化的三个"Why"入手解释为何在项目开发中我们应该使用组件化,并通过一个头部导航栏组件从无到有的组件拆分演示了组件的注册,以及最基础的父子组件通信的方式。
重点需要理解的是组件化的设计,我们要如何对组件拆分进行计划,不能一味的深度拆分,需要考虑到组件之间通信的流向清晰。
当组件的层级过于复杂时应当采用什么方式来处理,比如使用Vuex、Pinia等,使得数据源可被/易被追溯。最后补充了全局注册组件的方法,以及对该方法进行横向切分的建议。
➡️查看项目代码