深入浅出 solid.js 源码 (十四)—— DOM 操作实现

·  阅读 75

这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

上一节看了编译部分,编译后的 DOM 操作是调用 dom-expressions 下面的方法实现的,这一节来看一下 dom-expressions 仓库中一些逻辑的源码实现。

整个 dom-expressions 仓库分为 client 和 server 两个入口,代表 client side render 和 server side render,我们现在只看 client 部分逻辑。client render 的入口是 render 函数,这部分之前阅读过,不过之前的主要关注点在 createRoot 的调用,这里来看 DOM 相关的操作。createRoot 传入的回调执行时会完成 dom 的创建,我们给 render 传第二个参数作为根节点,在这一步会调用 insert 把我们根组件执行的结果插入到根节点中。

insert 这个方法我们前面见过很多次,这里来看一下源码实现:

export function insert(parent, accessor, marker, initial) {
  if (marker !== undefined && !initial) initial = [];
  if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
  effect(current => insertExpression(parent, accessor(), current, marker), initial);
}
复制代码

这里的 accessor 可能是一个普通的 dom 节点,也可能是 solid 组件函数,对于普通的节点可以直接插入,如果是函数组件,这里是在 effect 回调中插入,effect 对应的是 solid 中的 createEffect,这里相当于把插入节点的动作也交给 solid 的 effect 来调度。

我们可以继续阅读实际执行插入的 insertExpression 函数,这个函数的源码实现比较长,不过逻辑不难理解,就是判断待插入数据的类型,最终调用 appendChild 或 replaceChild 来完成节点插入的动作。

前面还见过一个 template 的调用,这个函数式构建 html 布局的关键。现代浏览器都一定程度支持 WebComponents 规范,想创建一块 html 最有效的方式就是借助 template 标签,这里的 template 方法也是这样做的:

export function template(html, check, isSVG) {
  const t = document.createElement("template");
  t.innerHTML = html;
  if ("_DX_DEV_" && check && t.innerHTML.split("<").length - 1 !== check)
    throw `The browser resolved template HTML does not match JSX input:\n${t.innerHTML}\n\n${html}. Is your HTML properly formed?`;
  let node = t.content.firstChild;
  if (isSVG) node = node.firstChild;
  return node;
}
复制代码

之后只需要调用 template 的 cloneNode 就可以创建一组 dom 节点了。

另一个比较重要的部分就是事件处理了,最终添加事件绑定是通过 delegateEvents 函数完成的,它接收事件名数组作为参数,源码实现如下:

export function delegateEvents(eventNames, document = window.document) {
  const e = document[$$EVENTS] || (document[$$EVENTS] = new Set());
  for (let i = 0, l = eventNames.length; i < l; i++) {
    const name = eventNames[i];
    if (!e.has(name)) {
      e.add(name);
      document.addEventListener(name, eventHandler);
    }
  }
}
复制代码

这里的 Set 就是为了避免同一事件重复添加,这里面实际的有效代码就是 addEventListener,这里添加的 eventHandler 也是内置的实现,它完成了事件的分发和模拟:

function eventHandler(e) {
  const key = `$$${e.type}`;
  let node = (e.composedPath && e.composedPath()[0]) || e.target;
	// some logic ...
  while (node !== null) {
    const handler = node[key];
    if (handler && !node.disabled) {
      const data = node[`${key}Data`];
      data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
      if (e.cancelBubble) return;
    }
    node =
      node.host && node.host !== node && node.host instanceof Node ? node.host : node.parentNode;
  }
}
复制代码

这里会拿到触发事件的目标节点,之后获取 event定义,这个event 定义,这个 就是在编译阶段 solid 添加的事件处理函数,之后调用这个事件处理函数就触发了事件监听。

上面这些就是比较常见的一些 solid 编译生成的 DOM 操作逻辑的实现,对这部分感兴趣可以继续深入阅读 dom-expressions 仓库。

收藏成功!
已添加到「」, 点击更改