JS进阶-组件封装|青训营

55 阅读2分钟

组件封装:指web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。
本文以实现一个轮播图组件为例:
首先是html的部分,html使用无序列表实现

<div id="my-slider" class="slider-list">
        <ul>
            <li class="slider-list_item">
                <img src="xxx">
            </li>
            <li class="slider-list_item">
                <img src="xxx">
            </li>
            <li class="slider-list_item-seleted">
                <img src="xxx">
            </li>
            <li class="slider-list_item">
                <img src="xxx">
            </li>
        </ul>
    </div>

CSS部分:

#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-seleted{
            position: absolute;
            transition: opacity 1s;
            opacity: 0;
            text-align: center;
        }

        .slider-list_item-seleted{
            transition: opacity 1s;
            opacity: 0;
        }

JS部分

class Slider {
  constructor(id) {
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll(
      ".slider-list_item,.slider-list_item-selected"
    );
  }

  getSelectedItem() {
    const selected = this.container.querySelector(".slider-list_item-selected");
    return selected;
  }

  getSelectedItemIndex() {
    return Array.from(thiss.items).indexOf(this.getSelectedItem());
  }

  SlideTo(index) {
    const selected = this.getSelectedItem();
    if (selected) {
      selected.className = "slider-list_item";
    }
    const item = this.items[index];
    if (item) {
      item.className = "slider-list_item-selected";
    }
  }

  SlideNext() {
    const index = this.getSelectedItemIndex();
    const nextIndex = (index + 1) % this.items.length;
    this.SlideTo(nextIndex);
  }

  SlidePrevious() {
    const index = this.getSelectedItemIndex();
    const preIndex = (this.items.length + index - 1) % this.items.length;
    this.SlideTo(preIndex);
  }
}

该部分定义了一个slider类,其中定义了许多轮播图的操作。
但是这种方式其实还是不够灵活,当轮播图中的内容要改变时,我们需要去修改很多代码,此时我们思考是否能够将组件插件化:将其中的控制元素抽取成插件,组件与插件之间通过依赖注入的方式建立联系。

 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();
              });

              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';
              });
            }  
          }

          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();
              });
            }  
          }

          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();
              });
            }  
          }

组件封装总结:

  1. 结构设计
  2. 展现效果
  3. 行为设置 API(功能) Event(控制流)
    考虑到后续需要在该功能中加入底部小圆点标记的功能,因此将组件通用模型抽象出来
class Component{
            constructor(id, opts = {name, data:[]}){
              this.container = document.getElementById(id);
              this.options = opts;
              this.container.innerHTML = this.render(opts.data);
            }
            registerPlugins(...plugins){
              plugins.forEach(plugin => {
                const pluginContainer = document.createElement('div');
                pluginContainer.className = `.${name}__plugin`;
                pluginContainer.innerHTML = plugin.render(this.options.data);
                this.container.appendChild(pluginContainer);
                
                plugin.action(this);
              });
            }
            render(data) {
              /* abstract */
              return ''
            }
          }

其中,registPlugins就是用于注册插件的方法

在开发过程中,我们很容易就将js和html混在一起写,特别是功能点由少到多的时候,因此在开发之前,需要我们进行详细的设计,尽量考虑到多方因素,将js抽象出来,以便于后续代码的维护与修改。