Good code is determined by the engineer, not the programming language.

Tip 2 component encapsulation

Example of a carousel graph

Components are units of Web pages that contain templates (HTML), functions (JS), and styles (CSS). Good components have encapsulation, correctness, extensibility, and reuse.

The first edition

HTML: unordered list structure implementation

<div id="my-slider" class="slider-list">
  <ul>
    <li class="slider-list__item--selected">
      <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
    </li>
  </ul>
</div>
Copy the code

CSS: Absolute positioning overlaps images together, using a selector to select the image to display

#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;
}
Copy the code

JavaScript: Get the precursor and successor of the image and perform the switch operation

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(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    const item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected'; }}slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1)
      % this.items.length;
    this.slideTo(previousIdx); }}const slider = new Slider('my-slider');
slider.slideTo(3);
Copy the code
  • The Slider component:
    • getSelectedItem(): Gets the currently selected image;
    • getSelectedItemIndex(): Gets the index of the currently selected picture;
    • slideTo(): Multicast to the correspondingidxThe picture;
    • slideNext(): get the precursor image index;
    • slideNext(): get the following image index;

Tips: The API should be designed to allow atomic operations, a single responsibility, and flexibility.

The problem with this is that the image is coupled to the image status bar (state synchronization). If you need to modify the image status bar, code modification can be very troublesome. How to make the picture and the picture status bar can be independent of each other, but also coordinate with each other?

Reconfigurable points:

  • Decoupled using custom events;

The second edition

HTML

<div id="my-slider" class="slider-list">
  <ul>
    <li class="slider-list__item--selected">
      <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
    </li>
  </ul>
  <a class="slide-list__next"></a> <! -- -- -- > 2
  <a class="slide-list__previous"></a> <! -- -- -- > 2
  <div class="slide-list__control"> <! -- -- -- > 2
    <span class="slide-list__control-buttons--selected"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
  </div>
</div>
Copy the code

CSS

#my-slider{
  position: relative;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  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;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255.255.255.0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next..slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0.0.0.0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons..slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}

Copy the code

JavaScript

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;

    const controller = this.container.querySelector('.slide-list__control'); / / 3
    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) {this.slideTo(idx);
          this.stop(); }}); controller.addEventListener('mouseout'.evt= >{
        this.start();
      });
      
      this.container.addEventListener('slide'.evt= > { / / 4
        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'; })}const previous = this.container.querySelector('.slide-list__previous'); / / 3
    if(previous){
      previous.addEventListener('click'.evt= > {
        this.stop();
        this.slidePrevious();
        this.start();
        evt.preventDefault();
      });
    }
    
    const next = this.container.querySelector('.slide-list__next'); / / 3
    if(next){
      next.addEventListener('click'.evt= > {
        this.stop();
        this.slideNext();
        this.start(); evt.preventDefault(); }); }}getSelectedItem(){
    let selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    let selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    let item = this.items[idx]; / / 4
    if(item){
      item.className = 'slider-list__item--selected';
    }
  
    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    let currentIdx = this.getSelectedItemIndex();
    let nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    let currentIdx = this.getSelectedItemIndex();
    let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  start(){ / / 1
    this.stop();
    this._timer = setInterval(() = >this.slideNext(), this.cycle);
  }
  stop(){ / / 1
    clearInterval(this._timer); }}const slider = new Slider('my-slider');
slider.start();
Copy the code

In the second version of the code, start() and stop() were first added to improve the automatic playback of the wheel (1). At the same time, in HTML (2) added a picture of the status bar and before and after button control. In the constructor (3), the registered mouse event is monitored through the listener and the corresponding API is operated. It is noted here that stop() should be called to clear the timer when the mouse is hovering, and the timer will be reset after the mouse is moved out, so as to avoid the conflict caused by manual and automatic playback. How do I keep the picture in sync with the picture status bar while playing?

Listen to the slide custom event (sending image index) (4), obtain the index of the currently displayed picture, and update the status of small dots in the picture status bar according to the index of the picture. In this way, the listener does not need to know how the slideTo() function itself is implemented, just listening for the Slide custom method; SlideTo () doesn’t need to know how the image status bar is implemented, just dispatch its own Slide event. Let control information flow between listeners and custom events (control flow). If you later need a new component to synchronize with the photo state, you can also listen to Slide to do so.

Notice that in this version of the code, there is too much code in the constructor because in the constructor, we need to deal with the image status bar and the image toggle button controls, which is obviously not coupled properly.

Reconfigurable points:

  • Lack of scalability and reusability, consider continued decoupling;
  • The control (picture status bar, picture before and after switch button) is extracted as a plug-in;
  • The relationship between plug-ins and components is established by dependency injection.

Tips: In JavaScript, you should limit the number of lines of code in a single function to 30, and consider refactoring if you exceed that.

Version 3: Plugins

HTML

<div id="my-slider" class="slider-list">
  <ul>
    <li class="slider-list__item--selected">
      <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
    </li>
  </ul>
  <a class="slide-list__next"></a>
  <a class="slide-list__previous"></a>
  <div class="slide-list__control">
    <span class="slide-list__control-buttons--selected"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
  </div>
</div>
Copy the code

CSS

#my-slider{
  position: relative;
  display: inline-block;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  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;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255.255.255.0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next..slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0.0.0.0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons..slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}
Copy the code

JavaScript

class Slider{
  constructor(id, cycle = 3000){ / / 1
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = cycle;
  }
  registerPlugins(. plugins){ / / 2
    plugins.forEach(plugin= > plugin(this));
  }
  getSelectedItem(){
    const selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    const item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }

    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler)
  }
  start(){
    this.stop();
    this._timer = setInterval(() = >this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer); }}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(); }); }}const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext); / / 3
slider.start();
Copy the code

In the third version of the code, only three lines of constructor (1) remain. The most important change is that the registerPlugins() function (2) implements plug-in registration. In this function, we register the plug-in by iterating through the list of plug-ins. Note that the plug-in initialization depends on the component, but the component does not need to be aware of the plug-in’s existence, so we can pass the current component instance to the plug-in as a parameter for dependency injection. With this change, we separate the plug-in logic from the constructor. If you need to modify it later, you only need to replace or delete the registered plug-in (3).

Although we implement decoupling in JS, we also need to modify the corresponding HTML if we need to change it.

Reconfigurable points:

  • HTML template, easy to expand;

Fourth edition: templating

HTML

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

CSS

#my-slider{
  position: relative;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  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;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255.255.255.0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next..slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0.0.0.0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons..slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}
Copy the code

JavaScript

class Slider{
  constructor(id, opts = {images:[], cycle: 3000}){
    this.container = document.getElementById(id);
    this.options = opts;
    this.container.innerHTML = this.render(); / / 1
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = opts.cycle || 3000;
    this.slideTo(0);
  }
  render(){ / / 1
    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>`;
  }
  registerPlugins(. plugins){ / / 2
    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);
    });
  }
  getSelectedItem(){
    const selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    let item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }
    
    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler);
  }
  start(){
    this.stop();
    this._timer = setInterval(() = >this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer); }}const pluginController = {
  render(images){
    return `
      <div class="slide-list__control">
        ${images.map((image, i) => `
            <span class="slide-list__control-buttons${i===0?'--selected':' '}"></span>
         `).join(' ')}
      </div>    
    `.trim();
  },
  action(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'; }); }}};const pluginPrevious = {
  render(){
    return `<a class="slide-list__previous"></a>`;
  },
  action(slider){
    const previous = slider.container.querySelector('.slide-list__previous');
    if(previous){
      previous.addEventListener('click'.evt= >{ slider.stop(); slider.slidePrevious(); slider.start(); evt.preventDefault(); }); }}};const pluginNext = {
  render(){
    return `<a class="slide-list__next"></a>`;
  },
  action(slider){
    const previous = slider.container.querySelector('.slide-list__next');
    if(previous){
      previous.addEventListener('click'.evt= >{ slider.stop(); slider.slideNext(); slider.start(); evt.preventDefault(); }); }}};const slider = new Slider('my-slider', {images: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png'.'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg'.'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg'.'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'].cycle:3000});

slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
Copy the code

The content in the fourth version of the code HTML is greatly reduced, and the reduced content is templated by JS. The first step is to process a different number of image inputs, using the Render () method (1) to render the list of incoming images as an unordered list in HTML. Similarly, for plug-in (2), render the HTML of the plug-in in the render() method of the plug-in, and initialize the plug-in with the action() method of the plug-in.

Here we find that for any component, we always need to register the registerPlugins() plugin and render() template. Can further abstraction improve the extensibility and generality of components?

Reconfigurable points:

  • Abstract the common component model;
  • CSS templating;

Fifth edition: Abstraction

HTML

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

CSS

#my-slider{
  position: relative;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  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;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255.255.255.0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next..slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0.0.0.0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons..slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}
Copy the code

JavaScript

class Component{ / / 1
  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{
  constructor(id, opts = {name: 'slider-list', data:[], cycle: 3000}){
    super(id, opts);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = opts.cycle || 3000;
    this.slideTo(0);
  }
  render(data){
    const content = data.map(image= > `
      <li class="slider-list__item">
        <img src="${image}"/>
      </li>    
    `.trim());
    
    return `<ul>${content.join(' ')}</ul>`;
  }
  getSelectedItem(){
    const selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    const item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }

    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler);
  }
  start(){
    this.stop();
    this._timer = setInterval(() = >this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer); }}const pluginController = {
  render(images){
    return `
      <div class="slide-list__control">
        ${images.map((image, i) => `
            <span class="slide-list__control-buttons${i===0?'--selected':' '}"></span>
         `).join(' ')}
      </div>    
    `.trim();
  },
  action(slider){
    let controller = slider.container.querySelector('.slide-list__control');
    
    if(controller){
      let buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
      controller.addEventListener('mouseover'.evt= >{
        var 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;
        let selected = controller.querySelector('.slide-list__control-buttons--selected');
        if(selected) selected.className = 'slide-list__control-buttons';
        buttons[idx].className = 'slide-list__control-buttons--selected'; }); }}};const pluginPrevious = {
  render(){
    return `<a class="slide-list__previous"></a>`;
  },
  action(slider){
    let previous = slider.container.querySelector('.slide-list__previous');
    if(previous){
      previous.addEventListener('click'.evt= >{ slider.stop(); slider.slidePrevious(); slider.start(); evt.preventDefault(); }); }}};const pluginNext = {
  render(){
    return `<a class="slide-list__next"></a>`;
  },
  action(slider){
    let previous = slider.container.querySelector('.slide-list__next');
    if(previous){
      previous.addEventListener('click'.evt= >{ slider.stop(); slider.slideNext(); slider.start(); evt.preventDefault(); }); }}};const slider = new Slider('my-slider', {name: 'slide-list'.data: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png'.'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg'.'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg'.'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'].cycle:3000});

slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
Copy the code

In version 5, we abstracted a Component class containing only the constructor, the registerPlugins() plugin registry, and the Render () template. Any Component that needs to render can inherit Component directly, simply implementing its corresponding Render () method in the Component. Component itself can also be thought of as a mini-component framework.

You can also do another layer of abstraction for plug-ins, or you can change the way of thinking and use multi-tier components instead of differentiating between component plug-ins.

Steps to implement components: structural design, presentation, behavior design

The resources

  • Web development technology | MDN (mozilla.org)
  • Youth Camp Community (juejin. Cn)