不学会组件拆解,只能当个切图仔!以掘金首页为例带你进行组件拆解

5,687 阅读14分钟

我正在参与掘金创作者训练营第5期, 点击了解活动详情

前言

通常情况下在 写代码前 ,先设计页面结构、模块、组件、数据流转,再进行页面开发是一个比较顺畅的流程。
作者自身一直以来也是依据这套流程来进行的开发。同时,也认为大家的开发过程都是大同小异的。

遇到了什么问题?

直到一次次协助群里的兄弟们解决问题时,发现情况远比想象中复杂。

我经常看到的代码是:

image.png

这样的,

image.png

这样的,

image.png

甚至这样的...

这太不可思议了,我难以理解这样的代码是怎么维护的。新功能怎么添加,出问题了怎么查,面对一大片的 state 怎么办。

一个文件 数百行 甚至 上千行 代码稀松平常,最夸张的一个是一个复杂的后台页面,近万行 代码在一个文件中。

为什么我们需要组件拆解工具

作为开发者而言,分离关注点是提升代码避不开的问题。但作为前端而言,分离关注点的难度反而比后端更高。

虽然现代化的前端开发有组件这么一个天然的关注点分离工具,但是对于耦合了 数据逻辑结构样式 的前端开发者而言,经常会在多个关注点之间反复横跳。

那我们作为前端开发者,有一个优雅的组件拆解工具,关注点的分离由拆解工具帮助我们实现,而我们仅需依据拆解工具提供的思路进行开发即可。

此我尝试将该思路以一种步骤化的操作来实现,以便于它的 复用评估
且我们在开发中发现拆解工具的问题后,也便于拆解工具更好的优化迭代。

优势:

  • 复用
  • 测试
  • 迭代
  • 教学

💡 tips:
对于很多高级开发者而言,组件拆解工具可能是多余的,组件的拆解逻辑往往是一件本能的反应。

大家在一次次的开发中踩过的坑,以及对业务意图的理解,往往足以支持我们拆解出优雅的组件。这也是拆解工具多余的原因。

但这种拆解方式往往会带来一些问题:

  • 这种思维上的经验,很难固化成固定的流程
  • 在新员工入职时,很难通过简单易懂的语言传授给他们
  • 通过该方式拆解出的组件,质量难以评估
  • 在拆解思维出现问题时,由于思维的固化,很难对拆解过程进行迭代

关于代码长度与开发能力之间的关系

对于开发者来讲,开发能力越强的人,可能掌控的单文件大小越大。因为开发能力强,对应着 抽象能力、关注点分离能力、代码组织能力、解耦能力 都有不错的表现。

但往往代码能力越强的人,文件大小越稳定,不容易出现过大的文件。代码能力弱的人,反而代码越写越长,甚至出现几千行的代码。

我们需要给他们提供一个可以缩减代码体积的组件拆解工具以供他们写出更好的代码。

思路清晰,先说分析

关于组件分类

既然要做组件拆解工具,那么首要的内容一定是给组件进行分类。
组件没有分类,就像无根之水,一颗健壮的大树往往需要更粗壮的根部。

组件类型 为基础,才能做出更好的组件拆解工具,也让我们的拆解工具不是悬在空中、不可落地的泛泛之谈。

在这里我们的组件分为以下四类:

  • 布局模块
  • 业务模块
  • 功能组件
  • 基础组件

💡 tips:
这里的 布局模块 、 业务模块 通常意义上讲是一个容器,不承载具体功能的实现。
这里的 功能组件 、 基础组件 真正承载了功能的实现。

什么是布局模块

  • 定义
    • 在我们的开发过程中,前端往往以首层文件来做页面的结构。通常情况下,我们将整体页面的结构定义为 布局模块
    • 很多时候我们的布局模块就是我们这个页面的文件自身来承载这部分能力。
  • 意义
    • 定位
  • 示例
    • header、content、footer

什么是业务模块

  • 定义
    • 在我们的容器组件中,往往有一大片功能的关联性强 、位置相近,往往这么一块内容是为了实现同一种 意图 ,我们将这一部分内容作为一个 业务模块
  • 意义
    • 让我们的模块间 相互独立
    • 更好的确认组件之间的 数据关系
    • 明确我们这块内容的 意图
  • 示例
    • 文章列表、文章筛选、文章标签 共同组成了 文章展示模块

什么是功能组件

  • 定义
    • 在我们的业务模块中,是以一块块关联性强的业务组成的,每一个组件都承载着固定的某个功能,我们将这一部分内容作为一个 功能模块
  • 意义
    • 功能出现问题时更快的定位 问题点
    • 功能更新时不影响其他功能
    • 多次使用的功能组件可以抽离成 通用组件
  • 示例
    • 文章筛选器、推广文章、文章列表

什么是基础组件

  • 定义
    • 我们的开发过程中,除了业务模块、功能模块,还会有很多与业务无关的组件,小到一个 div ,大到一个 Card ,我们将这一部分内容作为一个 基础组件
  • 意义
    • 便于通用组件的复用
    • 和页面模块解耦
  • 示例
    • 各个组件库中的组件、自己封装的 BaseCard、List

以掘金首页为例,领悟组件分类(未完善)

我将以掘金首页为例,带大家理解 组件分类 的逻辑。

image.png
  • 红色 - 布局模块
  • 蓝色 - 业务模块
  • 紫色 - 功能组件
  • 未标 - 基础组件

关于拆解流程

我们在接到一个新需求时,通常是 功能页面体验优化功能模块BUG修复 ,我们这里以最经典的 功能页面 为基础,进行组件拆解流程化。其他需求类型可以根据场景选择对应的中间起点。

在一个页面拆解成一个个组件任务的过程中,分为以下几步:

  1. 确定数据(接口对接)
  2. 布局模块拆解
  3. 业务模块拆解
  4. 功能组件拆解
  5. 基础组件拆解
  6. 以终为始的进行组件的数据确立

下面我们基于流程一步步来展开讲。

确定数据(接口对接)

首先,我们要确认前端本质就是 页面 ←→ 数据 的映射。因此无论如何,我们的开发都是以数据为起点。

我们要确定我们页面需要的数据。该数据在接口对接时也可以起到引导作用。数据结构不一定要和接口完全一致,我们可以在中间层进行数据加工,但还是要保证接口数据基本符合我们的预期。

🔨 数据确认方法:

首先,我们要保证我们数据确认时,尽可能的找到所有数据。避免在真正的开发过程中,发现缺少数据,进而出现开发中的不确定性影响我们预期的开发速度。

然后,我们需要在所有数据的基础上对数据进行分类。分类原则:相互独立、完全穷尽。

最后,如果大家的接口对接在这一步完成了,可以对接口数据和本地数据进行思维上的映射,以便于我们在开发的过程中更顺畅。

在这个过程中,我们可以捎带考虑一下数据在页面上的流转。

🔨 数据确认原则:

  • 相互独立
    • 如很多开发者深恶痛绝的上百字段大接口
  • 完全穷尽
    • 不要在真正开发时,才发现缺漏数据

💡 tips:
由于自身负责前端相关内容,且后端同学比较容易沟通,所以在接口对接上有更大的话语权。
如果你们的后端比较强势,那么跟着后端数据走,在中间层做处理,也是避免矛盾的好办法。

布局模块拆解

这个阶段有一个前提,就是相应部门给出了页面的原型图。这里原型图的来源(UI / 产品)视公司情况而定。

现阶段有了产品给的原型图,我们便要基于 原型图 设计出该页面的页面结构。这里页面的结构基本上就是咱们容器组件的原型了。

🔨 布局模块拆解方法:

根据页面的布局,以原型图为基础,以实际使用的布局方案(flex、grid、float)为思路,拆解出页面的布局,从而实现布局模块的拆解。

如果页面复杂度不高,可以直接将整体布局写在页面文件中,以页面文件作为多个布局模块的集合。

💡 tips:
容器组件一般整个在页面组件中。如果页面结构过于复杂,可以考虑拆分到多个组件。

业务模块拆解

这一阶段有两个前提,一是我们的数据已经确认无误,二是我们的页面结构(布局模块)已经敲定。

数据有了、容器位置也有了。基于这 位置 + 数据,设计该页面的业务模块。将相近的功能合并,无关的功能拆分。

🔨 业务模块拆解方法:

根据页面上的内容,区分出不同的意图,以意图来拆分模块。比如:文章搜索、文章标签、文章列表,本质上都是用来给用户展示我们的文章。

同时,到这步我们还需要捎带考虑接口调用位置、数据存放位置,以便于我们后期确定我们组件的 props、state。本质上,业务模块承载了 功能组件基础组件 的存放,也承载了 数据统筹 的能力。

🔨 业务模块拆解原则:

  • 业务模块通过意图拆分,每个模块有自己的独立意图
  • 不同的容器组件下的相同意图,可以考虑拆分成两个业务模块

💡 tips:
不一定页面上所有的内容都属于某一个模块,可能是页面下直接是功能组件。

功能组件拆解

前面我们确定了 布局模块业务模块 ,基本上已经可以确定我们都有哪些功能组件了,接着我们基于上面两个模块进行 功能组件 的拆解。

🔨 功能组件拆解方法:

这里我们基本上已经知道我们的页面都有哪些功能,如: 文章列表 、 文章筛选器 、 文章标签。我们只要基于我们的理解把这些功能组件罗列出来即可。

这里有两个我们需要注意的点,一是我们的功能组件应该是随时可以脱离页面独立生存的。二是不要耦合多个功能在一个功能组件里。

🔨 功能组件拆解原则:

  • 可以脱离业务模块独立生存
  • 不要耦合多个功能

基础组件拆解

基础组件往往完全落在业务之外,不依赖业务存在。可能通过一层简单的业务封装,成为功能组件。它往往是以一个组件库、一个通用的 components

🔨 基础组件拆解方法: 对于基础组件的感知,往往依赖于我们拆解出来的功能组件。 如: 我们有一个文章列表的功能组件,那么对应的我们可能需要 List 、 Card 两个基础组件。

这些基础组件一般以组件库导入,在组件库不满足我们的需求时,它就会落在我们的通用 components 文件中。

需要注意的有两点,一是我们的功能组件完全不存在业务意图,只是基础能力。二是基础组件完全脱离页面独立存在。

🔨 基础组件拆解原则

  • 完全脱离模块
  • 不包含业务意图

💡 tips:
基础组件本质上是功能组件的基础组成部分,如:用户卡片 和 卡片。

以终为始的进行组件数据确立

到这里,我们的组件其实已经完全拆解完成了。但是我们的数据流转并没有确立,因此我们以终为始的进行数据定义(props、data、global、redux、vuex、localStorage 等)。

我们这时候应该以基础组件为开始,倒着进行各个组件的属性定义。倒着定义的原因是:避免我们的属性定义和外部功能耦合导致的数据混乱。

万事俱备、只欠东风(怎么做)

光讲理论往往会导致我们的思想停在空中,导致泛泛而谈。

因此,我们以掘金的首页为例,来走一遍我们的组件拆解流程。

image.png

确定数据(接口对接)

基础我们对页面的理解拆解数据。

// 文章类型
const articleType = ['综合', '关注', '后端', '前端', 'Android', 'IOS', '人工智能', ...]

// 文章列表
const articleList = [
	{
    id: '1',
		userName: '掘金酱',
		createDate: '2022-07-14',
		title: '「掘金创意开发大赛来了!记录你的灵感迸发时刻!」封面',
    cover: '<https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4981df21a04f444ca98a4ee72fa4f824~tplv-k3u1fbpfcp-no-mark:720:720:720:480.awebp?'>,
		desc: '掘友们好,掘金创意开发大赛来了!你是否曾有过一瞬,灵感创意迸发,但却没有及时记录下的时候?掘金将助你焕发灵感,感受创意乐趣!'
		showCnt: '11000',
		likeCnt: '123',
		commentCnt: '23',
		tags: ['javascript', 'typescript', '前端']
	}
]

// 广告列表
const advertiseList = [
  {
    cover: 'https://xxxx.png',
    link: 'https://www.xxxx.com'
  }
]

// 作者列表
const authorList = [
  {
    id: '1',
    nickname: 'sincenir',
    signature: '基础大过天',
    level: '4'
  }
]

💡 tips:
现在有很多平台承载了数据到页面之间的映射。这时,前端开发者的需求可能会锐减。大多数后台页面的前端开发者都会失业,但现在看起来市面上对这类平台的信任度不高。 但随着产品一步一步完善,终会有完全替代的一天,希望大家提前做好准备。 现有品牌列表:

  • 黑帕云
  • 明道云
  • 速融云
  • 简道云
  • 启业云

布局模块拆解

整体部分我们分为三部分,详见下图。

该布局我们以页面文件承载。 image.png

业务模块拆解

基于我们的 布局+数据 ,我们的业务模块可以分为以下几个:

  • 类型管理模块
  • 文章模块
  • 广告模块
  • 作者榜单模块
image.png

功能组件拆解

基于我们的 布局模块业务模块 ,我们可以拆解出以下的功能组件。

  • 类型管理模块
    • 类型展示组件
    • 类型编辑组件
  • 文章模块
    • 文章排序组件
    • 文章列表组件
      • 文章卡片组件
  • 广告模块
    • 广告卡片组件
    • app推广组件
  • 作者榜单模块
    • 作者榜单卡片组件
      • 作者列表组件

基础组件拆解

这里便是开发功能组件所需的地基。暂以组件库处理。

以终为始的进行组件的数据确立

我们将以倒叙的方式,处理组件的数据。

下面我们以一个模块(文章模块)为例,演示该问题的处理。

文章卡片组件所需数据:

interface IArticle {
    id: number
    userName: string
    createDate: string
    title: string
    cover: string
    desc: string
    showCnt: number
    likeCnt: number
    commentCnt: number
    tags: string[]
}

props: { ...IArticle }

文章列表组件

interface IArticleListProps {
    list: IArticle[]
}

文章排序组件

interface IArticleSortProps {
    sortWayList: string[]
    sortWay: string
    onChangeSortWay: (sortWay: string) => void
}

文章模块:文章模块需要的是统筹数据、请求接口。

写在最后

组件拆解对于维护开发风格统一,后期维护的好处是不言而喻的。

林林总总的也写了一个月了,明儿活动就结束了,可算是赶出来了。

文章的示例还有点儿不太全面,后期会整理到 git 上供大家参考。