我会用一个大的章节来介绍组件化,主要目的在于分享组件化的开发思维,组件化由何而来,让大家对于组件化底层思维有一个了解,并且有自己的独立思考。为深入的掌握好组件化提供一个方向与思路。
这些底层思维,是进一步掌握好组件化实践「React/vue」的核心基础,如果没有这些思维打底,你可能对于框架,会长期仅仅停留在简单运用的层面,而无法真正的感知到组件化思维的巨大威力,开发出具备更强可维护性的代码。
因此,这个大的章节不会进一步分享如何深入掌握 React 等应用层面的知识,那需要至少一整本书才能说完,但会提供一个更为便利掌握它的底层基础,因此同样非常重要。大家明确好学习目的,做到有的放矢。
组件化是前端独有的开发思维。是在模块化的基础之上发展而来的更高效的开发手段。
得益于 webpack 的横空出世,组件化的思维有了落地实现的基础。
那么什么是组件化呢?
我们知道,一个按钮,通常有如下几部分共同组成
<button class="btn"></button>
click
等回调事件.btn {}
...
这些资源共同构成了一个完整的按钮。
那么,我们在使用时,最简单的方式就应该把按钮当成一个独立个体引入一次,即可呈现完整的按钮
import Button from './components/Button'
但是在模块化开发的结构中,我们做不到这样的便利,我们必须单独处理 html 标签,js 逻辑,css 样式。漏掉了一个,就无法呈现完整的按钮。
我们使用一个案例来体会一下模块化与组件化的区别。
首先使用 create react app 创建一个项目
npx create-react-app tab --template typescript
项目地址:点击这里查看示例代码
我们先使用模块化的方式来完成一个 Tab 组件。
首先置空 App.tsx 的内容。
默认创建的项目还有许多其他冗余的文件,我们不关注,就当他们不存在。
10import React from 'react';20import './App.css';3040function App() {50return (60<div className="App">70</div>80);90}1011export default App;
然后,在 html 中,新增片段
10<div id="tab-root">20<div class="titles">30<div class="item active" data-index="0">标题1</div>40<div class="item" data-index="1">标题2</div>50<div class="item" data-index="2">标题3</div>60</div>70<div class="contents">80<div class="item active">内容1</div>90<div class="item">内容2</div>10<div class="item">内容3</div>11</div>12</div>
在 src/tab.css
中,新增如下样式代码
1020body {30margin: 0;40}5060html, body {70height: 100%;80}9010#tab-root {11width: 300px;12margin: 20px auto;13border: 1px solid #CCC;14height: 400px;15}1617.titles {18display: flex;19height: 44px;20border-bottom: 1px solid #CCC;21}2223.titles .item {24flex: 1;25height: 100%;26text-align: center;27line-height: 44px;28font-size: 12px;29}3031.titles .item.active {32background-color: orange;33color: #FFF;34}3536.contents {37position: relative;38height: 100%;39}4041.contents .item {42position: absolute;43top: 0;44left: 0;45bottom: 0;46right: 0;47display: none;48align-items: center;49justify-content: center;50}5152.contents .item.active {53display: flex;54}
在 src/tab.ts
中新增如下逻辑代码
10const titles = document.querySelector<HTMLElement>('.titles');20const contents = document.querySelector('.contents');3040if (!titles || !contents) {50throw new Error('element not exist.')60}7080let index = 0;9010titles.onclick = (event) => {11const activeTitle = event.target as HTMLElement;1213if (!activeTitle) {14return;15}1617const aindex = Number(activeTitle.dataset.index);1819if (aindex !== index) {20titles.children[index].classList.remove('active');21contents.children[index].classList.remove('active');2223activeTitle.classList.add('active');24contents.children[aindex].classList.add('active');25index = aindex;26}27}
到这里,关于 tab 的功能都开发好了。
当我们使用时,直接在入口文件 index.tsx
中,引入如下资源就行。
1import './tab'2import './tab.css'
我们分析一下这样做的弊端。
在代码组织上,tab 组件相关的 html 片段,css 样式,以及 js 逻辑,我们并没有把他们当成一个整体来处理。分散到了不同的位置。因此在使用时,我们需要明确的知道 tab 组件的每一个组成部分,然后分别处理。
这会在使用上给我们带来很大的麻烦。
我们来尝试一下组件化的使用方式。
我们尝试基于 React 封装一个 Tab
组件。
创建目录 src/Tabbar
用于存放 Tab 组件的所有模块。
在该目录下新建 index.css
用于存放样式。
10/* src/Tabbar/index.css */20body {30margin: 0;40}5060html, body {70height: 100%;80}9010.tab_container {11width: 300px;12margin: 20px auto;13border: 1px solid #CCC;14height: 400px;15}1617.titles {18display: flex;19height: 44px;20border-bottom: 1px solid #CCC;21}2223.titles .item {24flex: 1;25height: 100%;26text-align: center;27line-height: 44px;28font-size: 12px;29}3031.titles .item.active {32background-color: orange;33color: #FFF;34}3536.contents {37position: relative;38height: 100%;39}4041.contents .item {42position: absolute;43top: 0;44left: 0;45bottom: 0;46right: 0;47display: none;48align-items: center;49justify-content: center;50}5152.contents .item.active {53display: flex;54}
在该目录下新建 index.tsx
创建组件。
10// src/Tabbar/index.tsx20import React, { Component } from 'react';30import './style.css';4050const defaultTabs = [{60title: 'tab1',70content: 'tab1'80}, {90title: 'tab2',10content: 'tab2'11}, {12title: 'tab3',13content: 'tab3'14}]1516class Tab extends Component {17state = {18index: 019}2021static defaultProps = {22tabs: defaultTabs23}2425switchTab = (index) => {26this.setState({27index28})29}3031render() {32const { tabs } = this.props;33const { index } = this.state;3435return (36<div className="tab_container">37<div className="titles">38{tabs.map((tab, m) => (39<div40className={m === index ? 'item active' : 'item'}41key={m}42onClick={() => this.switchTab(m)}43>44{tab.title}45</div>46))}47</div>4849<div className="contents">50{tabs.map((tab, n) => (51<div className={n === index ? 'item active' : 'item'} key={n}>{tab.content}</div>52))}53</div>54</div>55);56}57}5859export default Tab;
封装之后,假如我们要在 App.tsx
中使用该组件,就只需要引入一次即可。而不用关心 Tabbar 组件的组成细节。
10// src/App.tsx20import React from 'react';30import './App.css';4050+ import Tabbar from './Tabbar'6070function App() {80return (90<div className="App">10+ <div>组件化 Tab</div>11+ <Tabbar />12</div>13);14}1516export default App;
组件化开发思维,是在模块化的基础之上,将所有的资源都当成模块来处理,不同的模块经过合理的合并,能够拼合成为一个完整的组件。
因此,组件化是模块化的延伸。
一个基础组件,由多个模块组成。
一个组件,也可以当成一个模块来处理,参与合并成为别的组件。
组件也可以进行自由组合,合并成为新的组件。
最终整个项目,呈现出来的结果就是一棵组件树。
组件化的手段,是通过组合来实现页面,但是,掌握组件化的核心,是要学会拆分。
合理的组件拆分,能够让我们的开发效率得到极大的提高。因此在这个基础上,我们可以简单对组件进行一个简单的分类,以方便我们快速具备拆分组件的能力。
一、基础组件
通常由模块组成,或者由基础组件组成。基础组件是页面拆分的最小单位。例如 Icon 图标组件,Button 按钮组件,以及更多常见的 UI 组件。「可以参考 ant deisgn」
通常每个项目都需要单独封装一套基础组件,用于快速实现页面逻辑。或者有的项目在不需要单独设计 UI 的情况下,可以直接引入开源的基础组件。
二、容器组件
容器是组件化的重要概念。顾名思义,一个容器组件中,在使用时可以内置其他基础组件,用于组合成为更为复杂的组件。
三、业务组件
业务组件通常无法在不同的项目之间共享。因此业务组件往往需要单独根据实际需求进行封装。
业务组件有两种情况。
一种是容器组件与基础组件组合而成的复杂组件。该复杂组件在项目中多次使用。另外一种是具备异步逻辑的组件。
四、页面组件
页面组件是所有组件组合而成的最终页面。
一个完整的单页应用,是由多个页面组件组成。通过切换路由访问不同的页面。
组件拆分,要在实践中慢慢沉淀经验,做好组件拆分,定能在开发时事半功倍。