具代表性的面試主題

前端面試:如何設計宣告式 Shadow DOM 與漸進式 hydration?

前端困難
Offer.cc 編輯團隊發佈 更新

題幹

伺服器渲染的商品卡片必須在 JavaScript 載入前可用,保持樣式隔離,並在 hydration 後互動。請設計宣告式 Shadow DOM 方案,解釋 open 與 closed、slot、降級、升級、串流與測試。

題目與範圍

頁面從伺服器串流輸出商品卡片。每張卡片要在沒有 JavaScript 時顯示有意義內容,元件樣式要隔離,客戶端腳本到達後再接入行為。請使用宣告式 shadow root,並說明不支援該屬性的瀏覽器,以及自訂元素在 HTML 已存在後才升級的情況。

核心能力是瀏覽器渲染、元件邊界與 hydration 正確性,因此歸入 frontend

面試官考察什麼

第一,是否知道支援時,帶 shadowrootmode="open""closed" 的 template 會由 HTML 解析器轉換為 shadow root?

第二,能否說明同一 host 只有第一個宣告式 shadow root 會附著,後續實例仍是 template,需要明確降級策略?

第三,能否在伺服器 HTML 與客戶端升級間保持 slot、樣式、可存取性和表單行為?

第四,能否區分 open 與 closed?closed 只隱藏 shadowRoot 引用,不是安全邊界。

第五,能否避免 hydration 或串流傳輸中的重複渲染、重複監聽和事件遺失?

先釐清的問題

  • 支援哪些瀏覽器與伺服器渲染回應模式?
  • 自訂元素會立即註冊,還是可能延遲升級?
  • 哪些子節點使用 slot,是否需要消費者透過 ::slotted 樣式化?
  • 測試與整合是否需要 open,還是產品要求 closed?
  • 頁面會串流傳送巢狀元件,還是一次傳送完整 root?
  • 不支援宣告式解析時採用什麼降級?

30 秒回答框架

「我會由伺服器輸出帶宣告式 shadow root 的元件和語意化 light DOM 降級內容,custom element 升級時重用已解析的 root,不重新生成樹。公開內容使用 slot,樣式放在 root 內;open 或 closed 是整合決策,不是安全控制。透過特性偵測處理不支援的瀏覽器,hydration 保證冪等,事件只繫結一次,測試相容性、串流順序、slot、可存取性和升級時機。」

分步作答

第一步:輸出語意化 root

伺服器輸出 host 與宣告式 template。保持文字與控制項有意義,讓回應在腳本前仍可用。工具或整合需要檢查時選擇 open;只有元件契約需要隱藏引用時才選 closed。

html
<product-card>
  <template shadowrootmode="open">
    <style>:host { display: block }</style>
    <article><slot name="title"></slot><button>購買</button></article>
  </template>
  <span slot="title">鍵盤</span>
</product-card>

slot 分配屬於公開契約。除非同時控制可存取性語意,不要在 shadow 樹和降級內容中重複標題。

第二步:定義升級契約

custom element 註冊時先偵測是否已有 shadow root,不能再次呼叫 attachShadow。只初始化一次、只繫結一次事件,並保留伺服器節點。若瀏覽器沒有建立 root,再從 template 建立或保留相容的 light DOM 降級。

第三步:特性偵測與降級

用小型解析探針或相關 template 屬性偵測支援情況。不支援的瀏覽器仍應收到語意化 light DOM;客戶端可以稍後升級,但過渡期間不能隱藏內容。

第四步:保持 slot 與樣式

slot 把 light DOM 子節點投影到 shadow 樹。文件要說明具名 slot、預設 slot 與 ::slotted 的樣式邊界。元件樣式放在 root 內,透過自訂屬性或 parts 暴露刻意的外部介面。

第五步:處理串流與巢狀

串流傳輸可能先到 host,再到巢狀 slot 子節點或 custom element 定義。把解析順序當成正常狀態:子節點到達後 slot 應更新,升級發生在串流結束前後都要安全,不能替換 host 產生第二棵樹。

第六步:無重複地接入行為

只繫結一次事件,重複卡片可在 root 層委派。初始化狀態放在私有欄位或 WeakMap,不要放在消費者能覆寫的公開屬性。接入行為時保留表單狀態與焦點。

第七步:測試瀏覽器與可存取性邊界

測試支援與不支援的解析器、open 與 closed、一個與多個宣告式 root、slot 延遲到達、升級先於或晚於解析、鍵盤焦點、標籤、表單提交和網路中斷後的 hydration,斷言只有一棵互動樹且事件不重複。

示例回答

「我會串流輸出語意化 host 和一個宣告式 shadow root,用具名 slot 放置公開內容,樣式留在 root 內。custom element 升級時先檢查已有 root,重用伺服器標記而不是替換。open 便於檢查,closed 只隱藏引用,並非安全邊界。

我會做解析支援偵測並保留 light DOM 降級。串流和延遲升級都是正常狀態,因此 slot 投影和初始化必須冪等。測試涵蓋不支援的瀏覽器、巢狀 root、晚到的 slot、焦點、表單、可存取性和重複事件。」

常見錯誤

  • 每次升級都呼叫 attachShadow root 或狀態遺失 → 重用已解析 root。
  • 把 closed 當安全機制 → 只是隱藏引用 → 按封裝能力說明。
  • 重複降級與 shadow 內容 → 讀屏可能播報兩次 → 定義唯一可存取來源。
  • 假設 slot 一定先到 → 串流子節點模型錯誤 → 測試晚到投影。
  • 用內部選擇器樣式化 slot 內容 → 規則不能穿越邊界 → 使用 ::slotted、parts 或自訂屬性。
  • 每次渲染都繫結事件 → 點擊觸發多次 → 保證 hydration 冪等。
  • 刪除 light DOM 降級 → 不支援瀏覽器出現空白 → 保留語意化伺服器標記。

追問

追問 1:宣告式 shadow root 一定會附著嗎?

不一定。取決於解析器支援和合法 mode。不支援時 template 仍是普通內容,需要降級或客戶端升級。

追問 2:為什麼每個 host 只有一個 root?

解析器只附著第一個宣告式 root,後續 template 留待明確處理,避免競爭 root。

追問 3:何時選擇 open?

測試、整合或受控擴充需要 root 引用時選擇 open;它不授予或撤銷安全權限。

追問 4:串流傳輸中 slot 如何工作?

host 可以先存在,slot 子節點隨後到達,投影會更新;測試兩種到達順序。

追問 5:如何避免 hydration 不匹配?

保持伺服器與客戶端契約穩定,重用現有節點,讓初始化冪等,不生成第二棵樹。

追問 6:如何測試 closed root?

透過公開契約測試可見行為、焦點、事件和可存取性,不直接讀取 shadowRoot

公開來源

同類題目