#常用配方
以下片段都只使用公开 API,省略了应用自身的错误处理与数据加载。组件覆盖、解析函数与插件属于可信代码,接入前请按安全模型审查。
#站点主题与深色模式
库的样式由 CSS 变量驱动,容器上的 class 与属性都可以参与选择器:
.wiki-article {
--wtr-text: #1f2328;
--wtr-accent: #0969da;
--wtr-border: #d0d7de;
--wtr-placeholder-bg: #f6f8fa;
}
.wiki-article[data-theme="dark"] {
--wtr-text: #e6edf3;
--wtr-accent: #4493f8;
--wtr-border: #30363d;
--wtr-placeholder-bg: #161b22;
}import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
<Wikitext source={source} className="wiki-article" containerProps={{ "data-theme": theme }} />;同一份源码在浅色与深色下的效果:
主题示例
正文、强调 与 链接。
Template not expanded: Infobox
- title=模板占位
主题示例
正文、强调 与 链接。
Template not expanded: Infobox
- title=模板占位
import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
const source = `== 主题示例 ==
'''正文'''、''强调'' 与 [[链接]]。
{{Infobox|title=模板占位}}`;
const css = `
.recipe-theme {
--wtr-text: #1f2328;
--wtr-muted: #57606a;
--wtr-border: #d0d7de;
--wtr-accent: #0969da;
--wtr-accent-soft: #ddf4ff;
--wtr-placeholder-bg: #f6f8fa;
padding: 12px;
border: 1px solid #d0d7de;
border-radius: 8px;
background: #ffffff;
}
.recipe-theme--dark {
--wtr-text: #e6edf3;
--wtr-muted: #8b949e;
--wtr-border: #30363d;
--wtr-accent: #58a6ff;
--wtr-accent-soft: #172a45;
--wtr-placeholder-bg: #161b22;
border-color: #30363d;
background: #0d1117;
}
.recipe-theme + .recipe-theme { margin-top: 12px; }
`;
export default function Demo() {
return (
<div>
<style>{css}</style>
<div className="recipe-theme">
<Wikitext source={source} lang="zh-CN" />
</div>
<div className="recipe-theme recipe-theme--dark">
<Wikitext source={source} lang="zh-CN" />
</div>
</div>
);
}变量清单见样式与组件。
#站内路由与图片 CDN
把路由与媒体地址交给宿主,库只负责校验与渲染。把解析函数放在模块作用域,避免每次渲染重新准备:
import type { WikitextImageResolver, WikitextLinkResolver } from "react-wikitext/hast";
const resolveLink: WikitextLinkResolver = (request) => {
if (request.kind !== "internal") return undefined;
const page = request.page ?? request.target;
const fragment = request.fragment ? `#${request.fragment}` : "";
return { href: `/wiki/${encodeURIComponent(page)}${fragment}`, target: "_self" };
};
const resolveImage: WikitextImageResolver = (request) => ({
src: `https://cdn.example.com/wiki/${encodeURIComponent(request.name)}`,
alt: request.alt ?? request.name,
...(request.width === undefined ? {} : { width: request.width }),
...(request.height === undefined ? {} : { height: request.height }),
});<Wikitext source={source} prepareOptions={{ resolveLink, resolveImage }} lang="zh-CN" />返回值语义与策略见链接与图片。
#模板卡片
不展开模板,但可以把调用显示成站点自己的卡片:
import { Wikitext } from "react-wikitext";
import { WikitextFragment, type WikitextTemplateComponentProps } from "react-wikitext/view";
import "react-wikitext/styles.css";
function TemplateCard({ displayName, node }: WikitextTemplateComponentProps) {
return (
<aside style={{ border: "1px solid #d0d7de", borderRadius: 8, padding: 12, margin: "8px 0" }}>
<strong>{displayName}</strong>
<WikitextFragment nodes={node.children} mode="block" />
</aside>
);
}
export default function Demo() {
return (
<Wikitext
source={"{{Infobox|title=示例|body=模板参数会显示在卡片里}}"}
lang="zh-CN"
semanticComponents={{ template: TemplateCard }}
/>
);
}WikitextFragment 必须在 WikitextView 的 Context 内使用,并且只能渲染当前文档中的节点。
#文章目录
目录不一定由库生成。需要站点侧栏时,直接读取渲染快照的 headings,并固定锚点策略:
import { useMemo } from "react";
import { createWikiparser } from "react-wikitext/parse";
import { prepareWikitext } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
import "react-wikitext/styles.css";
const parser = createWikiparser();
export function Article({ source }: { source: string }) {
const prepared = useMemo(
() => prepareWikitext(parser.parse(source), { headingIdStrategy: "mediawiki" }),
[source],
);
return (
<div className="article-layout">
<nav aria-label="目录">
<ol>
{prepared.headings.map((heading) => (
<li key={heading.nodeId} data-level={heading.level}>
{heading.id ? <a href={`#${heading.id}`}>{heading.text}</a> : heading.text}
</li>
))}
</ol>
</nav>
<WikitextView document={prepared} />
</div>
);
}mediawiki策略写入裸锚点,适合单实例页面。- 多实例页面用默认
instance策略;在视图内部可以用useWikitextIds().toHref取得实际链接。 - 页内自动目录仍然会生成;只想保留侧栏时,可以用宿主 CSS 隐藏
.wtr-toc,或在源码层面使用__NOTOC__。
#渲染失败上报
视图按顶层块隔离失败;解析与准备的错误需要调用方捕获:
import type { WikitextDocument } from "react-wikitext/ast";
import type { WikitextPrepareOptions } from "react-wikitext/hast";
import { prepareWikitext, WikitextPrepareError } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
function renderPrepared(document: WikitextDocument, prepareOptions: WikitextPrepareOptions) {
try {
return <WikitextView document={prepareWikitext(document, prepareOptions)} />;
} catch (error) {
if (error instanceof WikitextPrepareError) {
report(error.code, error.stage, error.cause);
}
throw error;
}
}<Wikitext
source={source}
onRenderError={(error, { node }) => {
report("render", error, { nodeId: node.id, nodeType: node.type });
}}
/>labels.renderFailed 控制块级占位的标题;错误码与处理方式见错误与诊断。