前端进击笔记 - 腾讯高级前端工程师 - 拉勾教育

上一讲介绍了如何对前端应用进行模块化和组件化设计,是一个对系统设计进行抽象的过程。实际上,我们在业务功能开发过程中,也会在很多地方使用到抽象的能力,比如将页面内容抽象为数据展示,将页面的呈现方式抽象为状态。

通过抽象的方式,我们可以将整个前端应用使用数据的方式来进行表达和描述,还可以配合配置化来减少硬编码、提升功能的灵活性。

今天我就主要介绍如何将应用抽象成数据,以及怎么配合配置化来提升代码的可维护性。

如何将应用抽象成数据

要将应用抽象成数据,需要进行两个步骤:

  1. 将应用进行模块化和组件化划分;
  2. 将这些模块和组件用数据的方式进行表达。

上一讲我介绍了对前端应用进行模块化和组件化划分的一些原则,下面我会直接介绍将页面划分成模块和组件的例子。

将页面划分成模块和组件

在进行系统设计和开发代码之前,产品经理首先会进行功能设计。产品经理在设计一个页面的时候,会根据内容和功能的不同,设计出不同的模块,将它们呈现在页面中。

对于前端开发来说,我们会拿到功能需求描述以及 UI 设计图。此时我们需要结合功能说明以及交互稿或者设计图,来进行逆向拆解,即把一个页面按照功能和内容划分出一个个的模块。

以我自己的博客为例:

15 | 提升开发效率:数据抽象与配置化 - 图1

我们直观地根据视觉来划分模块,可以分为三大块。

  • 头部:快速导航栏
  • 左侧:内容板块
  • 右侧:推广导航板块

上一讲我们说过,组件可理解为带有界面渲染的特殊模块,因此上述这些模块也可以视作为组件。论坛类、博客类的页面结构大多如此,除此之外还有视频类、电商类等各种角色的网站,大都可以划分为内容模块(详情页 / 列表页)、导航模块(TAB / 导航栏 / 工具栏)、功能组件(回到顶部 / 翻页组件)等。

对应用进行模块划分之后,我们可以将划分后的模块抽象成数据的方式来表达。

将模块 / 组件抽象成数据

通常来说,我们可以抽离出应用中存在变化的内容和动态获取的数据,再通过将这些数据与页面内容绑定的方式(比如使用前端框架进行绑定),来控制具体功能的展示。

我们可以通过数据的来源,将其分成内部数据(状态数据)和外部数据(动态数据)两种。

1. 内部数据(状态数据)

在一个应用的设计里,我们可能会有多个组件,每个组件又各自维护着自己的某些状态。其中,部分组件的状态会相互影响,所有状态的结合体便是应用最终的整体状态。这些状态维护在应用内部,可以通过数据的方式来表示,我们简单称之为内部数据(状态数据)。

怎么定义内部数据呢?最浅显、最直观的办法就是,这些数据来自应用本身,同时影响应用的呈现状态。如对话框的出现和隐藏、标签的激活和失效、进度条的状态等,都可以作为状态数据。

比如,用户在网页中的一些操作,点击某个按钮之后,会出现弹窗,这个弹窗的展示内容以及是否展示的状态可以用对象来描述。

  1. const dialog = {
  2. isShow: true,
  3. title: "弹窗标题",
  4. content: "弹窗内容"
  5. };

2. 外部数据(动态数据)

除了应用本身的状态数据,还有很多不属于应用状态的数据,比如文章内容、个人信息等,都是需要从其他地方(服务端、缓存、文件等)获取的。这些需要从外部获取,用于页面展示或是影响展示的一些数据,我们将它称作外部数据(动态数据)。

外部数据不同于内部数据,它并不会跟随着应用的生命周期而改变,也不会随着应用的关闭而消失。这些数据独立存在于外部,通过动态获取和注入的方式进入应用,从而影响应用的展示内容和功能逻辑。

举个例子,上面博客中的内容板块为列表页,这样的列表页可以用一个数组来表示:

  1. const articleList = [
  2. { id: 333, title: "文章标题", brief: "文章简介", date: "日期" }
  3. ];

这些数据需要从本地数据库中读取,或者通过 HTTP 请求从服务端获取,再根据获取结果渲染出每个列表的内容。

我们在设计模块和组件的时候,需要将这些内部数据和外部数据抽离出来,其中内部数据在组件和模块内部进行维护。当应用启动的时候,再通过注入外部数据的方式,使其可以正常运行。

将数据与应用抽离

要怎么理解将数据与应用抽离呢?如果将应用比作是一个公司,公司里所有的桌椅、电脑等设备都是静态的,每个工位可理解为一个组件,同时办公室也可以认为是大一点的组件或是模块。

那么在我们这个公司里:

  • 内部数据是椅子的位置、消耗的电量、办公室的照明和空调状态等;
  • 外部数据是行政人员、技术人员、设计人员等各种人员流动。

每天上班的时候,一个个的工作人员来到公司里,开始干活,此时公司也开始运作。

  1. # 将公司和人分开(下班后)
  2. --------------------------------------------------------
  3. 公司
  4. --------------------------- ---------------------------
  5. | |
  6. | |
  7. | 办公楼 |
  8. | |
  9. | |
  10. --------------------------- ---------------------------
  11. # 在公司正常运作的时候
  12. --------------------------------------------------------
  13. 公司
  14. --------------------------------------------------------
  15. | |
  16. | |
  17. | 办公楼 |
  18. | |
  19. | |
  20. --------------------------------------------------------

当然,大家不只是站在工位里这么简单,我们会与各种物件进行交互和反馈(挪动桌椅、开灯开空调等),人与人之间也会相互交流和影响(对需求、方案评审等)。

如果我们每个人的工位都是随机的,在工作过程中会很不方便,所以大家会被有规律有组织地分别隔离到每个办公室、隔间里面。

  1. # 按照组织进行分隔
  2. --------------------------------------------------------
  3. 公司
  4. --------------------------------------------------------
  5. | | | | | |
  6. | | | | | |
  7. |-------- | 办公楼 | --------- |
  8. | | | | | |
  9. | | | | | |
  10. --------------------------------------------------------

同样的,我们在设计应用的时候,除了需要考虑如何划分模块、对应用数据进行抽象,还需要将其有规律地管理。当我们将模块和组件的状态和内容抽象成数据之后,就可以将这些数据独立出来进行管理,也可以更好地解决模块 / 组件间耦合、依赖、通信等问题。

我们还能观察到,桌椅和其他办公设备其实也可以通过外部获取,也就是说,内部数据同样可以转换为外部数据,通过动态获取的方式来恢复应用之前的状态。比如某个表单在二次编辑的时候,需要恢复为提交时的状态,包括勾选框是否选中、输入框填写的内容等。

那么,我们将这些数据和应用抽离之后,可以用来做些什么呢?我们可以通过变更数据的方式来更新应用的状态,在第 9 讲中我们也有介绍数据驱动的编程方式。

我们还可以通过设计合适的状态机,来解决像 Web 页面这种天然异步系统中事件被多次触发问题,比如用户连续点击某个按钮,通过状态判断是否需要进行相应的逻辑处理等。

除此之外,当我们对数据进行抽象和分离之后,还可以很方便地实现应用的配置化。

实现应用的配置化

配置化的思想不仅仅存在于前端或者是某个领域,大多数的系统和功能设计,都可以用领域抽象、数据抽离、配置化等方式,减少需求开发和维护的成本。

将应用中的数据抽离之后,我们可以对这些数据进行配置。

可配置的数据

前面我们介绍了影响应用状态的数据,其中包括了与界面有关和与界面无关的数据。

当我们希望对某个产品进行配置化调整的时候,一般会使用运营管理平台来进行运营和管理。通过搭起一整套的运营管理平台,我们可以通过平台进行配置,管理应用的状态和数据,包括:

  • 界面展示内容,如活动文案、广告内容、推荐位等;
  • 功能逻辑,比如有效时间的计算、活动上线和下线、是否需要用户输入信息等。

如果应用功能逻辑通用性较好、复杂程度较低,我们甚至可以通过配置的方式快速搭建出前端页面,比如活动类配置系统、表单类配置系统等。

对于一些功能简单的页面,页面结构、功能比较相似,区别在于文案不一致、模块位置的调整、颜色的改变等。相比于通过复制粘贴然后调整逻辑,通过抽象和配置化的方式,我们节省重复性的工作,同时还避免了项目过多、重复代码难以维护等问题。

以上这种简单页面的配置,基本上有两种实现方式:

  1. 根据配置生成静态页面的代码,直接加载生成的页面代码;
  2. 实现通用的功能逻辑,在加载页面的时候动态加载配置数据,从而生成展示的页面。

配置化的核心大概是场景分析和功能拆解,所以抛开使用场景来做一个所谓 “通用” 的配置化是不现实的。为了做出合适的配置化功能,我们需要把问题范围局限在解决特定的场景,比如用于电商活动的页面、表单提交类的页面等。

以一个组件开发为例,我们来看看怎么实现组件的配置化。

组件配置化

以下图中的卡片组件来作为例子:

15 | 提升开发效率:数据抽象与配置化 - 图2

为了方便地表达数据绑定,后面的代码会基于 Vue 框架实现。

我们可以实现以下的配置化能力:状态和展示内容可配置、样式可配置、功能逻辑可配置。

1. 状态和展示内容可配置

例如,我们需要一个对话框,其头部、正文文字、底部按钮等功能都可支持配置,我们可以用代码这样表示。

  1. <div class="my-dialog" :class="{'show': isShown}">
  2. <header v-if="cardInfo.hasTitle">{{cardInfo.title}}</header>
  3. <section v-if="cardInfo.hasContent">{{cardInfo.content}}</section>
  4. <footer>
  5. <button v-for="button in cardInfo.buttons">{{button.text}}</button>
  6. </footer>
  7. </div>

通过这样的方式,我们可以:

  • 通过cardInfo.hasTitle来控制是否展示头部;
  • 通过cardInfo.buttons来控制底部按钮的数量和文字。

通过这种配置方式,我们可以控制组件中具体某些功能的状态和展示内容。

2. 样式可配置

样式的配置,通常是通过class来实现的,比如可以使用条件语句进行判断,并绑定不同的class

  1. <!-- 自己拼接完整 class -->
  2. <button :class="'my-dialog__btn--' + (isA ctived ? 'actived' : 'inactived')">Submit</button>
  3. <!-- 也可以将 class 拆分 -->
  4. <button class="my-dialog__btn" :class="isActived ? 'actived' : 'inactived'">Submit</button>

通过这样的方式,我们可以控制界面的展示样式。如今很多框架会通过在class里添加随机 MD5 这样的方式,来保持局部作用域的class样式不受其他地方影响。

除了class之外,我们当然也可以直接将条件语句和style进行绑定,来控制具体的某个样式。

3. 功能逻辑可配置

举个例子,我们的这个卡片可以是视频、图片、文字三者其中之一的卡片。

  • 视频:点击播放。
  • 图片:点击新窗口查看。
  • 文字:点击无效果。

这种时候,我们可以使用这两种方式来实现功能逻辑的配置化。

  • 通过控制 UI 展示来控制点击事件的处理:每个模块(视频、图片、文字)绑定自己的点击事件,同时通过配置控制哪个模块的展示,从而控制事件的处理逻辑。
  • 通过逻辑判断来控制点击事件的处理:绑定的点击事件里,根据配置来进行不同的事件处理,展示不同的效果。

可以看到,一个组件中可以通过配置的方式来控制它的状态、展示内容、样式,甚至是功能逻辑。而我们的应用常常是通过不同的组件和模块组成的,同样可以通过配置的方式来控制应用的各个状态、内容以及功能。

小结

今天我主要介绍了应用中常见的数据抽象,通过对应用进行合适的数据抽象,并将这些数据从应用中抽离从而实现应用的配置化,我们可以减少需要开发的代码量,提升系统的可维护性。

配置化的思想可以用在各个地方,我们也常常将其称为 “一切皆可配置化”。

实际上,除了页面内容、应用状态的配置化,我们甚至可以实现接口的配置化,比如 GraphQL 可用于自定义的接口 API 查询。

除了这些,你觉得还有哪些问题可以通过配置化来解决的呢?欢迎在留言区进行讨论。