在很久很久以前,工程师们通过 jQuery 操作 DOM 来在网页上实现一些交互。

后来,工程师们捣鼓出了框架。React 和 Vue 之类的前端框架让工程师们可以专注于描述数据和 UI 之间的关系,而不是直接去操作它们。渐渐地,没有人用 jQuery 了。

但万一我还是需要操作 DOM 呢?

几个月前我做了一个脚本,可以给网页版微博增加一些一键转发,可以给网页版微博增加一些一键转发(一键 awsl)的功能。由于涉及到大量直接操作 DOM 的需求,让我不得不又考虑引入 jQuery 来简化这些操作。

且慢。

1202 年了,谁还用 jQuery 啊。

于是我开始思考,1202 年了,如果我需要一个「jQuery」那么它应该具备什么特性?

  • 现代的模块化系统,而不是像 jQuery 那样直接暴露一个全局的 $
  • 必须是 TypeScript,JS 之光。裸写 JavaScript 的都是恶魔。
  • Less is more

一顿编码之后,我做出来一个原型。我将它取名叫 dro。其实本来想叫 tquery 的,已经被抢注了。至于为啥叫 dro,我也忘了。但是它足够短。

基础查询

import { $, $$ } from 'dro';
const container = $<HTMLElement>(document, '.foo');
const children = $$(document, '.foo a');

我认为像 jQuery 那样 $() 永远返回一个数组是为了简化问题而带来更多问题。所以参照以前使用 MooTools 的经历,决定用 $()$$() 来区分需要得到一个元素还是多个元素的场景(本质上就只是 querySelectorquerySelectorAll 的 alias 罢了)。

你还注意到,dro 利用了 TypeScript 的泛型。$() 可以带上泛型参数来取得这个类型的结果。比如 $<HTMLInputElement>(document, '#username')

批量查询

import { $H } from 'dro';

interface MyForm {
  username: HTMLInputElement;
  password: HTMLInputElement;
  submit: HTMLElement;
}

const form = $H<MyForm>(document, {
  username: '.my-form input.user',
  password: '.my-form input.pass',
  submit: '.my-form button[type="submit"]',
});

console.log(form?.username.value);

我写那个脚本的时候发现了一个需求。我经常需要同时 query 多个元素,并且一一对它们判空。有些时候,它们也不是简单的 HTMLElement,而是 HTMLInputElement 之类带有更多特性的元素。于是我带来了 $H() 这个函数。

它同样利用了 TypeScript 的泛型特性。你需要先声明一个 interface 来描述你需要的字段和对应的类型,然后通过参数传给函数。同时传给函数的还有对应 interface 中每个字段的 CSS selector(利用 TypeScript 的特性,编辑器会提示你需要哪些字段,确保没有遗漏或拼错)。

如果 interface 中声明的元素通过对应的 CSS selector 全部能得到,那么 $H() 会返回这个对象。否则,只要任意一个 CSS selector 不满足,返回 null

其它一些 HTML 操作函数

import { append, create, html, attrs, style, on } from 'dro';

append(document.body, () => {
  const el = create('div');
  html(el, 'foo');
  attrs(el, {
    'class': 'foo',
  });
  style(el, {
    'margin-top': '200px',
  });
  on(el, 'click', () => console.log('lol!'));
  return el;
});

这一部分只满足了我自己开发那个脚本时的需求。以后可能会增改。

这里的 append() 传入的不是一个已经创建好的元素,而是一个 creator 闭包。这是因为我不希望这个需要被 append 到某处的元素创建过程中用到的一些变量被暴露到外面来。

总结

以上就是我对 dro 的一些设计思路。目前这个库仅作为原型发布。可能将来某一天想法成熟了,会作为一个严肃项目进行维护。当然,也可能咕咕咕…

感谢阅读。