跳到主要内容

模板与插槽

<template> 与普通的隐藏元素有什么不同?​

  • 语法检查: 浏览器会检查内部 HTML 语法, 脚本字符串模板则不会;
  • 任意顶层标签: 可放 <tr> 这类单独出现不合法、会被浏览器自动"修正"的标签;
  • 内容惰性: 内容被视为"文档之外", 样式不生效、脚本不执行、<video autoplay> 不播放;
  • 转活: 内容插入文档后才变成活的;
  • 能力边界: <template> 自身没有循环、数据绑定或变量替换, 需要在它之上自行实现;

如何插入 template 的内容?​

  • 取内容: template.content 是一个 DocumentFragment;
  • 插入特性: 插入 DocumentFragment 时插入的是它的子节点, 而不是它本身;
  • 复用: 用 tmpl.content.cloneNode(true) 克隆, 同一模板可反复使用;
const elem = document.createElement("div");
elem.append(tmpl.content.cloneNode(true));

document.body.append(elem); // 此时模板里的脚本才开始运行
  • 典型用法: 把克隆结果追加进影子根, 作为组件的初始结构;

插槽如何把 light DOM 组合进 shadow DOM?​

  • 插入点: 影子树里的 <slot name="X"> 接收 light DOM 中带 slot="X" 的元素;
  • 组合: 浏览器把两侧配对渲染, 结果称为 flattened DOM;
<user-card>
<span slot="username">John Smith</span>
</user-card>
<!-- shadow 内部: Name: <slot name="username"></slot> -->
  • 虚拟性: flattened DOM 只用于渲染与事件传播, 节点并未真正搬家;
  • 验证: document.querySelectorAll("user-card span").length 仍为 2;
  • 同名多个: light DOM 中多个同 slot 名的元素按出现顺序依次追加进同一插槽;

slot 属性只能写在哪里?​

  • 限制: 只有影子宿主的直接子元素上的 slot="..." 才有效, 嵌套元素上的会被忽略;
  • 原因: 组合只处理宿主的一级子节点;

默认槽与后备内容如何处理?​

  • 默认槽: 影子树中第一个不带 name 的 <slot>, 接收所有未被具名槽取走的 light 节点;
  • 后续无名槽: 被忽略;
  • 后备内容: <slot> 内部的内容是 fallback, 只有 light DOM 中没有对应填充者时才显示;
  • 追加顺序: 多个元素进入同一插槽时按出现顺序排列;

插槽内容变化时组件如何感知?​

  • 自动更新: 浏览器监视插槽, 增删被分配的元素会自动更新渲染;
  • 原地渲染: light 节点不被复制, 因此修改其内部内容立即可见;
  • slotchange 事件: 插槽首次被填充时触发, 之后被分配元素增/删/替换时触发, 但不包括被分配元素内部子节点的改动;
  • 事件目标: event.target 是该 <slot>; 影子根本身不能绑事件处理程序, 通常绑在影子根的第一个元素上;
  • 更深的改动: 需监听被插槽元素内部变化时用 MutationObserver, 见 观察者 API;

插槽相关的 JS API 有哪些?​

API作用
node.assignedSlot该节点被分配到的 <slot>
slot.assignedNodes({ flatten })分配给插槽的节点
slot.assignedElements({ flatten })同上, 只返回元素节点
  • 默认值: flatten 默认为 false;
  • flatten: true 时: 深入 flattened DOM, 返回嵌套插槽与后备内容;
  • 使用场景: 组件需要"知道自己展示了什么"时, 在 slotchange 里读 assignedElements;