这是我参与「第五届青训营 」伴学笔记创作活动的第 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部分后,我们得到如下效果:
行为:JS
最后我们使用js来控制页面行为,对于API的设计应该保证:原子性,操作单一、灵活。
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数组中,让组件拓展成可以指定任意多的图片的轮播图
抽象——组件框架
将通用的组件模型抽象出来
总结
- 组件设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计(封装性、正确性)
- 三次重构
- 插件化(扩展性)
- 模板化(扩展性)
- 抽象化(组件框架)(复用性)