【青训营】写好JS——组件封装(下)

495 阅读2分钟

这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

本篇将对上篇写的轮播图进行重构,使其具有更好的扩展性和可复用性。

重构:插件化

组件设计比较复杂的点往往在于控制的地方和组件本身有一定的耦合。

比如说上篇轮播图显示第几个图片,第几个小圆点就会标红,它们之间的状态就是耦合的,所以说我们需要解耦:

  1. 将用户控制元素(小圆点和左右箭头)抽取成插件
  2. 插件和组件之间通过依赖注入的方式建立联系

核心就是通过插件注册来实现依赖注入,插件初始化依赖于组件,而组件并不知道插件的存在:

registerPlugins(...plugins) {
  plugins.forEach(plugin => plugin(this));
}

然后把原本在构造器中的函数抽取成插件:

carbon (12).png

类增加一个addEventListener()的API:

addEventListener(type, handler) {
  this.container.addEventListener(type, handler);
}

最后调用即可:

const container = document.querySelector('.slider');
const slider = new Slider({ container });
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();

这样以来用户控制就解耦了,当PM有新的需求时,我们可以仅删除slider.registerPlugins()中的插件或者创建新的插件再添加。

但是问题是我们还要删除对应的HTML代码,这样还是要修改很多地方,所以下一步我们要把HTML模板化

重构:模板化

现在HTML部分只需要保留一个div:

<div id="my-slider" class="slider-list"></div>

在类中实现一个render函数来进行模板化的渲染,通过传入的参数返回正确的HTML模板:

constructor(id, opts = { images: [], cycle: 3000 }) {
  this.container = document.getElementById(id);
  this.options = opts;
  this.container.innerHTML = this.render();
  this.items = this.container.querySelectorAll('.slider-list__item, 
                                              .slider-list__item--selected');
  this.cycle = opts.cycle || 3000;
  this.slideTo(0);
}

render() {
  const images = this.options.images;
  const content = images.map(image => `
      <li class="slider-list__item">
        <img src="${image}"/>
      </li>    
    `.trim());

  return `<ul>${content.join('')}</ul>`;
}

这样参数也可以自由设定多张图片,这里我们把它添加到5张:

const slider = new Slider('my-slider', {
  images: [
    'https://gitee.com/mancuojie/typo/raw/master/202201241731487.jpg',
    'https://gitee.com/mancuojie/typo/raw/master/202201241731486.jpg',
    'https://gitee.com/mancuojie/typo/raw/master/202201241731485.jpg',
    'https://gitee.com/mancuojie/typo/raw/master/202201241731484.jpg',
    'https://gitee.com/mancuojie/typo/raw/master/202201241731483.jpg'],
  cycle: 3000
});

slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();

另外插件也要实现自己的render()函数,与组件同步:

carbon (11).png

通过action()正确初始化插件数据:

registerPlugins(...plugins) {
  plugins.forEach(plugin => {
    const pluginContainer = document.createElement('div');
    pluginContainer.className = '.slider-list__plugin';
    pluginContainer.innerHTML = plugin.render(this.options.images);
    this.container.appendChild(pluginContainer);

    plugin.action(this);
  });
}

这样我们就可以通过传参和修改插件注册来满足不同需求,大大增强了扩展性。

重构:组件框架

组件模板化之后,我们可以在做一层抽象,将组件模型抽象出来进一步提升通用性和扩展性。

image-20220125164925882

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

class Slider extends Component {
  
  ...
  
}

改进空间

  1. CSS模板化
  2. 将组件插件结构统一,也就是不再区分组件和插件,全部都是组件,组件和组件之间可以结合成一个大的组件,然后解决父组件和子组件的状态同步和消息通信,就可以完成一个相对完整的多层级的组件框架。