写好JavaScript的三大重要原则之组件封装| 青训营笔记

101 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

月影老师告诉我们写好JavaScript(包括其他语言)的三大重要原则:

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

组件封装

好的UI组件具备正确性、扩展性、复用性。

下面我们来看一个例子:轮播图

版本一

结构:HTML

轮播图是⼀个典型的列表结构,我们可以使⽤⽆序列表<ul>元素来实现。 __item表示元素项,--selected表示为选中状态

 <div id="my-slider" class="slider-list">
      <ul>
        <li class="slider-list__item--selected">
          <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59a09a68621745599dfb0d9a4b790332~tplv-k3u1fbpfcp-zoom-1.image">
        </li>
        <li class="slider-list__item">
          <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f4a65524f984a9db959a8c1d9de3f17~tplv-k3u1fbpfcp-zoom-1.image">
        </li>
        <li class="slider-list__item">
          <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/48f9da5dd8c942fdbb7bc3596e1d7b27~tplv-k3u1fbpfcp-zoom-1.image">
        </li>
        <li class="slider-list__item">
          <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4620125f386846a097fa70269498fc24~tplv-k3u1fbpfcp-zoom-1.image">
        </li>
      </ul>
    </div>

表现:CSS

  • 使用 CSS 绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modifier)这里是 --checked
  • 轮播图的切换动画使用 CSS transition
  #my-slider{
    position: relative;
    width: 790px;
  }

  .slider-list ul{
    list-style-type:none;
    position: relative;
    padding: 0;
    margin: 0;
  }

  .slider-list__item,
  .slider-list__item--selected{
    position: absolute;
    transition: opacity 1s;
    opacity: 0;
    text-align: center;
  }

  .slider-list__item--selected{
    transition: opacity 1s;
    opacity: 1;
  }

完成html、css部分后,我们得到如下效果:

image.png

行为:JS

最后我们使用js来控制页面行为,对于API的设计应该保证:原子性,操作单一、灵活。

image.png getSelectedItem():获取当前选中的图片元素:通过选择器.slider__item--selected获得被选中的元素。 getSelectedItemIndex():获取当前选中图片的索引值:返回选中的元素在items数组中的位置。 slideTo(idx):跳转到指定索引的图片。 slideNext():跳转到下一索引的图片:将下一张图片标记为选中状态。 slidePrevious():跳转到上一索引的图片:将上一张图片标记为选中状态。

通过手动调用API就可以使用轮播图了

以下为完成后的效果:

版本二

使用自定义事件来解耦。

在版本一中,我们实现了轮播图的自动播放和手动切换,但是未实现交互效果。所以,在这个版本中,我们要让用户可以控制我们轮播图的状态,所以我们需要一套控制流。

  • 我们在HTML中添加控制轮播图的一些元素,例如:两边的控制箭头,下面控制选图的小圆点
  • 在css中,添加css样式
  • 在js中加入控制流,让轮播图可以自动轮播,也可以手动控制,实现交互

以下为实现效果:

总结

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

思考:组件是指Web页面上抽出来一个个包含模版(HTML)、功能(JS)和样式(CSS)的单元。好的组件具备封装性、正确性、扩展性、复用性。到目前为止,我们其实只实现了封装性和正确性,但是扩展性和复用性还不行。那么我们接下来就该考虑重构这个组件

重构

解耦js————插件化

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

我们将用户控制的操作从组件中抽离出来,做成插件,这样就提高了组件的可扩展性。

用户的控制组件分为三个部分可以抽离成三个插件。

首先将小圆点的控制抽离成一个插件pluginController。插件接收的参数就是组件的实例,将控制流中的事件写在这里,插件中的逻辑就是之前构造函数中的逻辑。

function pluginController(slider){
  // 对小圆点的操作控制流
  const controller = slider.container.querySelector('.slide-list__control');
  
  if(controller){
    const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
    
    // 鼠标经过某个小圆点,就将此圆点对应的图片显示出来,并且停止循环轮播
    controller.addEventListener('mouseover', evt=>{
      const idx = Array.from(buttons).indexOf(evt.target);
      if(idx >= 0){
        slider.slideTo(idx);
        slider.stop();
      }
    });
    
    // 鼠标移开小圆点,就继续开始循环轮播
    controller.addEventListener('mouseout', evt=>{
      slider.start();
    });
    
    // 注册slide事件,将选中的图片和小圆点设置为selected状态
    slider.addEventListener('slide', evt => {
      const idx = evt.detail.index
      const selected = controller.querySelector('.slide-list__control-buttons--selected');
      if(selected) selected.className = 'slide-list__control-buttons';
      buttons[idx].className = 'slide-list__control-buttons--selected';
    });
  }  
}

将左翻页的控制抽离成插件pluginPrevious

function pluginPrevious(slider){
  const previous = slider.container.querySelector('.slide-list__previous');
  if(previous){
    previous.addEventListener('click', evt => {
      slider.stop();
      slider.slidePrevious();
      slider.start();
      evt.preventDefault();
    });
  }  
}

将右翻页的控制抽离成插件pluginNext

function pluginNext(slider){
  const next = slider.container.querySelector('.slide-list__next');
  if(next){
    next.addEventListener('click', evt => {
      slider.stop();
      slider.slideNext();
      slider.start();
      evt.preventDefault();
    });
  }  
}

最后通过注册插件registerPlugins来使用各种插件,得到我们的组件:

class Slider{
  constructor(id, cycle = 3000){
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = cycle;
  }
  registerPlugins(...plugins){
    // 这里的this就是组件的实例对象
    plugins.forEach(plugin => plugin(this));
  }
}

const container = document.querySelector('.slider'); 
const slider = new Slider({container}); 
// 注册三个插件
//在这里我们可以任意组合我们想要的插件
slider.registerPlugins(pluginController, pluginPrevious, pluginNext); 
slider.start();

解耦HTML——模板化

将HTML模板化,也就是让JavaScript来渲染组件的HTML,这样更易于扩展 在组件中加入render()渲染函数,用来渲染HTML 将图片放入一个images数组中,让组件拓展成可以指定任意多的图片的轮播图

抽象——组件框架

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

image.png

总结

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