islands 架构依赖服务端注入的标记(如 data-hydrate、注释、自定义元素)识别可水合区域,html 仅作静态容器与锚点;内联脚本破坏解耦,script type="module" 不满足水合校验与 dom 绑定要求。

Islands 架构不是 HTML 的原生特性,也不能靠写几个 <div> 或加个 <code>data-island 属性就生效。它本质是一种服务端渲染 + 客户端选择性水合的协作模式,HTML 只是承载它的“画布”——理解它,关键在看清 HTML 在其中的角色:静态容器、岛屿锚点、水合上下文。
HTML 中的 Islands 是怎么被识别出来的?
浏览器不认 “island”,框架也不靠 HTML 标签名判断。所谓“岛屿”,实际靠服务端注入的特定标记(如注释、data-* 属性或自定义元素)来划定边界。Astro 用 <counter></counter>(编译后生成带 data-hydrate 的 <div>),Fresh 用 <code><island></island> 自定义标签,HonoX 则依赖文件路径约定 + 服务端组件注册逻辑。
常见识别方式包括:
-
<div data-hydrate="idle" data-component="Counter">...</div>—— 框架扫描该属性后,懒加载对应 JS 并水合 -
<!--$ island: Counter --><div>...</div>—— 注释不参与 DOM 渲染,但服务端/客户端解析器可提取元信息 -
<counter is="server-rendered"></counter>—— 基于 Custom Elements,需提前customElements.define()
没这些标记,再复杂的交互组件也只会是纯静态 HTML —— 这正是 Islands 要的效果:默认静默,显式激活。
为什么不能直接在 HTML 里写 onclick 或内联脚本?
写了就破坏 Islands 的核心前提:解耦与可控水合。内联事件处理器(如 <button onclick="count++"></button>)绕过框架生命周期,无法被水合系统接管,也无法做 SSR 同构校验、服务端预计算、资源懒加载等优化。
更实际的问题是:
- 服务端渲染出的
onclick字符串,在客户端 hydration 阶段不会被重置或校验,容易导致状态错乱 - 无法按需加载 JS —— 即使用户根本没点那个按钮,JS 也已随页面加载完毕
- SEO 和无障碍工具可能把内联脚本当无效内容忽略,影响语义解析
正确做法是:HTML 只保留结构和初始状态(如 data-initial-count="5"),交由水合后的组件管理交互逻辑。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
<script type="module"></script> 能替代 Islands 吗?
不能。模块脚本解决的是代码组织和加载时机问题,但不解决“哪些区域需要交互”“如何复用服务端 HTML”“怎样避免全局水合阻塞”这些 Islands 关心的事。
典型反例:
- 一个
<script type="module"></script>加载了整个 React 应用,然后ReactDOM.createRoot().render()—— 这是 SPA,不是 Islands - 多个
<script type="module"></script>分别加载不同组件,但没和 DOM 片段绑定、没做状态同步、没跳过已渲染内容 —— 这是手动拼装,易出竞态,且无水合校验
Islands 的水合必须满足两个硬条件:1)客户端 JS 知道自己要接管哪一块 DOM;2)能比对服务端输出与客户端虚拟 DOM 是否一致。仅靠 type="module" 无法自动完成这两步。
手写 Islands HTML 时最容易漏掉什么?
最常被忽略的是“水合锚点”的完整性与隔离性。例如:
- 忘记给岛屿容器加唯一
id或data-island-id,导致多个同类型组件水合时互相覆盖状态 - 岛屿内部嵌套了另一个未声明为岛屿的交互区域(比如岛屿里有个
<select></select>,又手动绑了addEventListener),造成水合逻辑与原生行为冲突 - 服务端渲染的 HTML 包含了动态 class(如
class="active-{count}"),但客户端水合时没同步更新,出现样式闪烁或不一致
真正安全的做法:岛屿边界清晰、内部不自行接管事件、所有动态内容都通过 props 或 context 透传,而非 DOM 查询或硬编码节点操作。










