【青训营】- 跟着月影学JavaScript之怎么封装组件

1,910 阅读7分钟

本期主要的话题就是如何写好JS,介绍了一个常规的组件从最开始到迭代、一步步改进,重构过程的思路分析 。 从最简单的一个问题,一步步迭代实现出一个抽象的组件。

前言

写好JS的一些原则

  • 各司其责
  • 组件封装
  • 过程抽象

各司其责

html:负责结构 (Structural)

css:负责表现(Presentational)

js:负责行为(Behavioral)

image-20210817004813282

示例

写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式。

如果是你来实现,你会怎么做?

image-20210816215059596

版本一

如以下,我们实现了点击太阳变成白色,点击月亮变成黑色;

但是你觉得这个版本有什么问题?你会如何优化?

image-20210816220048582

存在问题

  • 未做到职责分离,即样式和行为未分离
  • 封装性不足,复用性不强
  • 不可配置
  • 全局污染
  • 如果不知道场景(要实现深浅模式),光看代码,不会知道这段代码是做什么的。

版本二

这一版比上一版好在哪里?还有没有其他方案?

image-20210816223614979

优于上一版的点

  • 去掉了直接操作style,代码各司其职,可读性,可扩展性,可维护性都会更好更高;
  • 使用了有描述性的单词,如night,大概知道是晚上的意思;

如果不懂这个版本太阳和月亮的处理,可以看这篇《深入了解::before 和 ::after 伪元素》里的【::before和::after的应用——》替换内容】,

版本三

利用纯CSS实现

知识储备

将一个 <label> 和一个 <input> 元素匹配在一起,你需要给 <input> 一个 id 属性。而 <label> 需要一个 for属性,其值和 <input> 的 id 一样。

:checked CSS 伪类选择器表示任何处于选中状态的radio(<input type="radio">), checkbox (<input type="checkbox">) 或("select") 元素中的optionHTML元素("option")。

原理解析:利用复选框的开关来控制样式

  • 首先写了一个隐藏的input,然后利用label,让太阳和月亮图标可以控制这个隐藏的input
  • 利用了input被选中状态的伪类:checked,判断当前是什么状态
  • 选中时,改变相关内容的样式

image-20210816225519438

结论

  • HTML/CSS/JS各司其责
  • 应当避免不必要的由JS直接操作样式
  • 可以用class来表示状态
  • 纯展示类交互寻求零 JS方案

组件封装

组件是指Web页面上抽出来一个个包含模版(HTML) 、功能(JS) 和样式(CSS) 的单元。好的组件具备封装性、正确性、扩展性、复用性。

示例

用原生JS写一个电商网站的轮播图,应该怎么实现?

首先设计HTML结构

轮播图是一个典型的列表结构,我们可以使用无序列表<ul>元素来实现。

image-20210817211546972

设计展现效果CSS表现

  • 使用CSS绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition

image-20210817211612672

JS行为设计

行为设计:API

API设计应保证原子操作,职责单一,满足灵活性。

现在我们来为我们的轮播设计API

image-20210817213428979

有了API之后,我们现在new一个这个对象,就可以直接操作了,比如切换下一张图

image-20210817213527765

行为设计:控制流

使用自定义事件来解耦。

上面我们已经实现了

  • 可以直接通过代码操作我们的轮播图,
  • 现在我们要通过【加控制】,即左右的箭头滑动和底下的四个小圆点
  • 来使我们在页面上就可以直接操作我们的轮播图

image-20210817214656575

image-20210817215213357

虽然当前已经可以实现轮播效果,但是可以看到我们的构造函数,除了做【初始化】之外,还做了一些构造函数不应该做的事情(如:各个操作按钮的逻辑都写到了构造函数里面)。所以我们应该要思考对这个组件进行优化。

总结:基本方法

  • 结构设计
  • 展现效果
  • 行为设计
    • API(功能)
    • Event(控制流)

思考:如何改进?

以上的问题

  • 构造函数太复杂
  • 可扩展性和灵活性不是很强

重构:插件化

解耦

  • 将控制元素抽取成插件
  • 插件与组件之间通过依赖注入
  • 方式建立联系

首先我们把写在构造函数里面的操作逻辑搬出来,简化构造函数

image-20210816233940041

image-20210816234237413

然后通过注册插件,把插件循环注入

image-20210817220507268

使用的时候,把上面分离的操作,利用registerPlugins注入进来,然后就可上面一样实现轮播的效果了

image-20210817220642235

优于上一个版本的点

  • 把插件的逻辑和轮播主体的逻辑分离开了。

  • 比如,你不想要小圆点了,则不把小圆点的方法注册进来即可,并不会影响到其他功能;

  • 如果你想要在页面其他地方加按钮,实现控制轮播,可以把这按钮写成一个方法注册进来即可;

虽然这个版本提升了可扩展性,但是还是没有那么的理想

  • 比如你可以通过注释依赖,停止小圆点的功能,但是页面的小圆点还是在的,还需要手动把小圆点的html删除。
  • 由此可见,我们只是把JS解耦了,但是html并没有

image-20210817221437894

重构:模板化

解耦

将HTML模板化,更易于扩展

image-20210817223301970

现在我们除了上面抽象的registerPlugins之外,再抽象出一个render方法,来渲染HTML容器。

image-20210817222325563

并且每个插件的方法也改造一下,让他有两个动作

  • 1、有个render方法控制自己的html
  • 2、有个action方法来控制自己的逻辑

image-20210817222815428

然后和之前一样注入到registerPlugins,当然registerPlugins也要做些修改

image-20210817223035196

优于上一个版本的点

  • 注释某个插件,即可去掉该功能,而不需要再手动去注释html

为了组件能有更好的扩展性,我们还可以继续优化

重构:组件框架

抽象

将通用的组件模型抽象出来

image-20210817224521935

我们抽象出一个Component

image-20210817224643921

然后我们就可以基于这个组件去实现我们的Slider

  • 首先继承Component
  • 然后Slider再去实现自己的constructor,render和其他的API方法

image-20210817224754947

同样的道理,除了可以这样实现Slider组件。 还可以实现一些其他符合这个规范的组件,如【树结构】什么的,也可以继承这个Component,再去实现自己的constructor,render和其他API方法的逻辑。

总结

  • 组件设计的原则:封装性、正确性、扩展性、复用性
  • 实现组件的步骤:结构设计、展现效果、行为设计
  • 三次重构
    • 插件化
    • 模板化
    • 抽象化(组件框架)

当前存在的问题

  • 虽然已经有了一定的可扩展性,灵活性,但是当前的【组件框架Component】还不算是完美,因为ta并不一定适用于所有的场景

继续思考:改进空间?

这个组件是否还有进一步改进的空间?

总结

在实际的开发场景中,项目通常会比较复杂,组件通常也会比较多,所以正常情况下是不太可能从零开始,用原生的js去一步步的设计出这样的组件。

组件抽象的部分通常在我们使用的库中已经实现了,如vue、react。但是我们还是应该去理解设计组件的一些思路,这样有助于你去理解这些框架本身的组件思想以及能够更好的去使用这些框架和库。

结语

字节青训营

本节课的讲师:字节前端/掘金开发负责人—月影

如以上有错误的地方,请在评论区中指出!


小可爱看完点个赞再走吧!😗