跳到主要內容
版本:3.5.2

用戶端架構

主題別名

主題會匯出 مجموعة من元件,例如NavbarLayoutFooter,用來呈現外掛程式向下傳遞的資料。Docusaurus 與使用者會匯入@themewebpack 別名來使用這些元件

import Navbar from '@theme/Navbar';

別名@theme可能會指向幾個目錄,其優先順序如下

  1. 使用者的website/src/theme目錄,一個具有較高優先權的特殊目錄。
  2. Docusaurus 主題套件的theme目錄。
  3. Docusaurus 核心提供的後備元件(通常不需要)。

這稱作分層式架構:優先權較高的層次提供的元件會覆蓋優先權較低的層次,讓它能進行互換。給定以下結構

website
├── node_modules
│ └── @docusaurus/theme-classic
│ └── theme
│ └── Navbar.js
└── src
└── theme
└── Navbar.js

website/src/theme/Navbar.js會在匯入@theme/Navbar時優先。此行為稱為元件互換。如果你熟悉 Objective C 中一個函式的實作可以在執行期間互換,那麼這裡概念相同,只是變更@theme/Navbar指向目標的方式!

我們之前談到了在 src/theme 中的「使用者介面主題」如何透過 @theme-original 別名來重複使用主題元件。一個主題套件還能透過從初始主題匯入元件,使用 @theme-init 匯入,來包裝來自另一個主題的元件。

以下是用此功能來針對預設主題 CodeBlock 元件,把它提升為 react-live 遊樂場功能的範例。

import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';

export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}

請檢查 @docusaurus/theme-live-codeblock 的程式碼以了解詳細資料。

警告

除非你想發布可重複使用的「主題增強器」(例如 @docusaurus/theme-live-codeblock),否則你可能不需要 @theme-init

要理解這些別名可能相當困難。讓我們想像一個案例,其中有三個主題/外掛程式和網站本身,它們都設法定義相同的元件。在內部,Docusaurus 將這些主題載入為「堆疊」。

+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` always points to the top
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` points to the topmost non-swizzled component
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` always points to the bottom
+-------------------------------------------------+

這個「堆疊」中的元件會依循下列順序來推入:預設外掛程式 > 預設主題 > 外掛程式 > 主題 > 網站,所以位於 website/src/theme 中的變更元件總是會出現在最上面,因為它是最後載入的。

@theme/* 總是指向最上層的元件,當 CodeBlock 被變更時,所有其他要求 @theme/CodeBlock 的元件都會收到被變更的版本。

@theme-original/* 總是會指向最上層的非變更元件。這就是為什麼你可以在變更元件中匯入 @theme-original/CodeBlock 的原因,它會指向「元件堆疊」中的下一個元件,由主題提供。外掛程式作者不應該嘗試使用它,因為你的元件可能是最上層的元件並造成自我匯入。

@theme-init/* 總是指向最下層的元件 — 通常,它來自於最初提供此元件的主題或外掛程式。嘗試增強程式碼區塊的個別外掛程式/主題能夠安全地使用 @theme-init/CodeBlock 來取得其基本版本。網站建立者通常不應該使用它,因為你可能想要增強最上層,而不是最下層的元件。@theme-init/CodeBlock 別名也可能完全不存在 — 只有當它指向與 @theme-original/CodeBlock 不同的元件時,Docusaurus 才會建立它,亦即當它是由多個主題提供的時。我們不會浪費別名!

用戶端模組

客戶端模組是網站套件的一部分,就像主題組件一樣。但是,它們通常具有副作用。客戶端模組是可以由 Webpack 導入的任何東西,例如 CSS、JS 等。JS 腳本通常在全域環境下工作,例如註冊事件聆聽器、建立全域變數...

甚至在 React 呈現初始使用者介面前,就會在全域層面導入這些模組。

@docusaurus/core/App.tsx
// How it works under the hood
import '@generated/client-modules';

外掛程式和網站都可以透過 getClientModulessiteConfig.clientModules 分別宣告客戶端模組。

客戶端模組在伺服器端呈現期間也會被呼叫,因此,請記得在存取客戶端端的全域變數前檢查 執行環境

mySiteGlobalJs.js
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

if (ExecutionEnvironment.canUseDOM) {
// As soon as the site loads in the browser, register a global event listener
window.addEventListener('keydown', (e) => {
if (e.code === 'Period') {
location.assign(location.href.replace('.com', '.dev'));
}
});
}

作為客戶端模組導入的 CSS 樣式表為的 全域

mySiteGlobalCss.css
/* This stylesheet is global. */
.globalSelector {
color: red;
}

客戶端模組生命週期

除了引入副作用外,客戶端模組也可以選擇性地匯出兩個生命週期函式:onRouteUpdateonRouteDidUpdate

由於 Docusaurus 會建立單頁式應用程式,因此,script 標籤只會在網頁第一次載入時執行,但不會在網頁轉換時重新執行。如果你有一些命令式 JS 邏輯會在每次載入新網頁時執行,例如用於處理 DOM 元素,用於傳送分析資料等,那麼這些生命週期即會派上用場。

對於每個路由轉換,都會有幾個重要的時機。

  1. 使用者按一下連結,導致路由器變更目前的位置。
  2. Docusaurus 會預載入下一個路由的資產,同時會持續顯示當前網頁的內容。
  3. 已經載入下一個路由的資產。
  4. 新的位置路由組件已呈現至 DOM。

onRouteUpdate 會在事件 (2) 中被呼叫,而 onRouteDidUpdate 會在 (4) 中被呼叫。這兩個函式都會收到當前位置和上一個位置(如果這是第一個畫面,則會是 null)。

onRouteUpdate 可以選擇性回傳一個「清理」回呼,此回呼會在 (3) 中被呼叫。例如,如果要顯示進度條,可以在 onRouteUpdate 中啟動一個setTimeout,並在回呼中清除setTimeout。(經典主題已經透過這種方式提供 nprogress 整合。)

請注意,新的網頁 DOM 只會在事件 (4) 中提供。如果你需要處理新的網頁 DOM,那麼你可能會想要使用 onRouteDidUpdate,此函式會在新網頁的 DOM 掛載後立即觸發。

myClientModule.js
export function onRouteDidUpdate({location, previousLocation}) {
// Don't execute if we are still on the same page; the lifecycle may be fired
// because the hash changes (e.g. when navigating between headings)
if (location.pathname !== previousLocation?.pathname) {
const title = document.getElementsByTagName('h1')[0];
if (title) {
title.innerText += '❤️';
}
}
}

export function onRouteUpdate({location, previousLocation}) {
if (location.pathname !== previousLocation?.pathname) {
const progressBarTimeout = window.setTimeout(() => {
nprogress.start();
}, delay);
return () => window.clearTimeout(progressBarTimeout);
}
return undefined;
}

或者,如果你正在使用 TypeScript 並且想要充分利用上下文鍵入時

myClientModule.ts
import type {ClientModule} from '@docusaurus/types';

const module: ClientModule = {
onRouteUpdate({location, previousLocation}) {
// ...
},
onRouteDidUpdate({location, previousLocation}) {
// ...
},
};
export default module;

這兩個生命週期都會在初次渲染時觸發,但它們不會在伺服器端觸發,因此你可以安全地在其中存取瀏覽器全域變數。

優先使用 React

用戶端模塊生命週期是純指令式的,而且你無法在其中使用 React 勾子或存取 React 語境。如果你的操作是狀態驅動的或涉及複雜的 DOM 處理,則你應該考慮改用交換元件