模板与插槽
<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;