「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
前言
在开发JS SDK时,『原生 or 框架』不同人不同场景都有不同的选择。本文源于实际项目场景,阐述一些在原生环境下『不顺手』问题的处理。
1. 模块化开发
// index.js
// 加入一个入口按钮
// 按钮点击增加侧边栏
// 点击空白隐藏侧边栏
// 侧边栏点击出现弹窗
// ...
如此写下去想想都头秃,像一篇流水账作文。
- 为了让读者快速定位到寻找的代码段,可以使用模块化(利用 webpack , gulp 等构建工具)。
- 为了弱化组织逻辑的时间顺序,可以使用面向对象,比如拆分 Class (拆分的粒度可以借鉴对框架中组件的使用)。
//index.js
Class Entry {} // 入口按钮
// dropdown.js
Class Dropdown { // 为指定dom绑定侧边栏
show() {} // 显示侧边栏
hidden() {} // 隐藏侧边栏
select() {} // 选中打开弹框
}
这里将组件开发的经验活用在了原生环境,代码结构更清晰了。
2. 维护 innerHTML 的秩序
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
用 JS 动态生成上面一段 dom 可能是这样的。
const ul = document.createElement('ul');
ul.className = 'list';
ul.innerHTML = [1, 2, 3].map(i => `<li>${i}</li>`).join('');
innerHTML 接受 HTML 序列化片段,因此我们可以使用模板字符串动态生成 dom 。不过这种方法存在局限性:
- 需要一个已有节点作为容器,无法在插入容器前操作 dom;
- 容器节点的属性设置仍然需要手动添加(比如class);
通过封装innerHTML可以克服上述局限性脱离容器生成一段 dom。
const wrap = document.createElement('div');
export const template2dom = <T extends HTMLElement>(template: string): T => {
wrap.innerHTML = template;
return wrap.children[0] as T;
};
const ul = template2dom(`
<ul class="list">${
[1, 2, 3].map(i => `<li>${i}</li>`).join('')
}</ul>`);
结合模块拆分可以将 dom 操作分为初始化渲染和动态修改。使用封装的 template2dom 可以得到模块的根节点,在根节点下 querySelector 可获取动态修改的节点,使用原生 dom API操作。
3. 清除事件
使用框架时,我们可以不必关心自己监听的事件是不是该移除了,通常框架会替我们解决。现在需要我们来解决:
- 当 dom 离开文档时,移除在其中绑定的事件;
- 当 dom 内有频繁更且需要绑定事件的节点,使用事件委托;
知易行难,移除事件的位置通常与注册事件不在一起,为了获取监听函数和 dom 增加了很多在 class 中共享的属性;由于疏忽很可能遗漏某个 removeEventlistener 。封装一个事件管理类作为一个基类可以很好解决。
export abstract class EventCleaner {
private readonly eventMap = new Map <HTMLElement, Set<{
name: keyof HTMLElementEventMap;
cb(e: HTMLElementEventMap[keyof HTMLElementEventMap]): unknown;
}>>();
addEventListener<T extends keyof HTMLElementEventMap>(
el: HTMLElement,
name: T,
cb: (e: HTMLElementEventMap[T]) => unknown
): void {
el.addEventListener(name, cb);
const events = this.eventMap.get(el);
if (events) {
events.add({name, cb});
}
else {
this.eventMap.set(el, new Set([{name, cb}]));
}
}
cleanEvent(): void {
for (const [dom, events] of Array.from(this.eventMap)) {
for (const {name, cb} of events) {
dom.removeEventListener(name, cb);
}
}
this.eventMap.clear();
}
}
这样只需要在每个派生类写入一个卸载方法,调用 cleanEvent
,即可清除该实例内监听的所有事件。
4. 样式隔离
样式隔离是 SDK 绕不开的问题。CSS Module 可在构建时修改 id 和 class 的形式,使得样式不会造成意外污染。
- hash:CSS Module 默认class,丢失语义性且随样式修改 (不利于下游覆盖样式);
- local + name[md5]:兼顾语义性、唯一性且不会随样式修改。
local + name\[md5\]
并不是 css-loader 支持的形式,需要在构建时提供 hash
var md5 = require('md5');
var affix = md5(path.parse(packageName)).slice(0, 5); // 将包名md5作为CssModule的后缀
// webpack 配置
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: `[local]_${affix}`,
}
}
}
]
}
以上是本人使用原生 JS 开发 SDK 的一些思考。其实主要围绕着梳理代码的秩序,原生API已足够强大不需要我扩展什么功能。前端框架给开发者更低的成本来养成逻辑清晰的习惯,也带来了一些思维惯性。希望能够取其精华弃其糟粕,无畏向前!
源码参考:github.com/anyblue/blu…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!