use-dom
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhat are DOM Components?
什么是DOM组件?
DOM components allow web code to run verbatim in a webview on native platforms while rendering as-is on web. This enables using web-only libraries like , , or any React web library in your Expo app without modification.
rechartsreact-syntax-highlighterDOM组件允许Web代码在原生平台的webview中完全原样运行,而在Web平台上则直接渲染。这使得你可以在Expo应用中使用仅支持Web的库,如、或任何React Web库,无需修改。
rechartsreact-syntax-highlighterWhen to Use DOM Components
何时使用DOM组件
Use DOM components when you need:
- Web-only libraries — Charts (recharts, chart.js), syntax highlighters, rich text editors, or any library that depends on DOM APIs
- Migrating web code — Bring existing React web components to native without rewriting
- Complex HTML/CSS layouts — When CSS features aren't available in React Native
- iframes or embeds — Embedding external content that requires a browser context
- Canvas or WebGL — Web graphics APIs not available natively
在以下场景中使用DOM组件:
- 仅支持Web的库 — 图表库(recharts、chart.js)、语法高亮工具、富文本编辑器,或任何依赖DOM API的库
- 迁移Web代码 — 将现有React Web组件引入原生平台,无需重写
- 复杂HTML/CSS布局 — 当React Native不支持某些CSS特性时
- iframes或嵌入内容 — 嵌入需要浏览器上下文的外部内容
- Canvas或WebGL — 原生平台不支持的Web图形API
When NOT to Use DOM Components
何时不使用DOM组件
Avoid DOM components when:
- Native performance is critical — Webviews add overhead
- Simple UI — React Native components are more efficient for basic layouts
- Deep native integration — Use local modules instead for native APIs
- Layout routes — files cannot be DOM components
_layout
避免在以下场景中使用DOM组件:
- 原生性能至关重要 — Webview会带来额外开销
- 简单UI — React Native组件在基础布局上更高效
- 深度原生集成 — 如需调用原生API,使用本地模块替代
- 布局路由 — 文件不能作为DOM组件
_layout
Basic DOM Component
基础DOM组件
Create a new file with the directive at the top:
'use dom';tsx
// components/WebChart.tsx
"use dom";
export default function WebChart({
data,
}: {
data: number[];
dom: import("expo/dom").DOMProps;
}) {
return (
<div style={{ padding: 20 }}>
<h2>Chart Data</h2>
<ul>
{data.map((value, i) => (
<li key={i}>{value}</li>
))}
</ul>
</div>
);
}在文件顶部添加指令来创建新文件:
'use dom';tsx
// components/WebChart.tsx
"use dom";
export default function WebChart({
data,
}: {
data: number[];
dom: import("expo/dom").DOMProps;
}) {
return (
<div style={{ padding: 20 }}>
<h2>Chart Data</h2>
<ul>
{data.map((value, i) => (
<li key={i}>{value}</li>
))}
</ul>
</div>
);
}Rules for DOM Components
DOM组件规则
- Must have directive at the top of the file
'use dom'; - Single default export — One React component per file
- Own file — Cannot be defined inline or combined with native components
- Serializable props only — Strings, numbers, booleans, arrays, plain objects
- Include CSS in the component file — DOM components run in isolated context
- 必须在文件顶部添加指令
'use dom'; - 单一默认导出 — 每个文件对应一个React组件
- 独立文件 — 不能内联定义或与原生组件混合
- 仅可序列化props — 字符串、数字、布尔值、数组、普通对象
- CSS需包含在组件文件中 — DOM组件在隔离环境中运行
The dom
Prop
domdom
属性
domEvery DOM component receives a special prop for webview configuration. Always type it in your props:
domtsx
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}每个DOM组件都会接收一个特殊的属性,用于配置webview。请始终在props中为其添加类型:
domtsx
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}Common dom
Prop Options
dom常用dom
属性选项
domtsx
// Disable body scrolling
<DOMComponent dom={{ scrollEnabled: false }} />
// Flow under the notch (disable safe area insets)
<DOMComponent dom={{ contentInsetAdjustmentBehavior: "never" }} />
// Control size manually
<DOMComponent dom={{ style: { width: 300, height: 400 } }} />
// Combine options
<DOMComponent
dom={{
scrollEnabled: false,
contentInsetAdjustmentBehavior: "never",
style: { width: '100%', height: 500 }
}}
/>tsx
// 禁用body滚动
<DOMComponent dom={{ scrollEnabled: false }} />
// 延伸至刘海区域(禁用安全区域内边距)
<DOMComponent dom={{ contentInsetAdjustmentBehavior: "never" }} />
// 手动控制尺寸
<DOMComponent dom={{ style: { width: 300, height: 400 } }} />
// 组合选项
<DOMComponent
dom={{
scrollEnabled: false,
contentInsetAdjustmentBehavior: "never",
style: { width: '100%', height: 500 }
}}
/>Exposing Native Actions to the Webview
向Webview暴露原生操作
Pass async functions as props to expose native functionality to the DOM component:
tsx
// app/index.tsx (native)
import { Alert } from "react-native";
import DOMComponent from "@/components/dom-component";
export default function Screen() {
return (
<DOMComponent
showAlert={async (message: string) => {
Alert.alert("From Web", message);
}}
saveData={async (data: { name: string; value: number }) => {
// Save to native storage, database, etc.
console.log("Saving:", data);
return { success: true };
}}
/>
);
}tsx
// components/dom-component.tsx
"use dom";
interface Props {
showAlert: (message: string) => Promise<void>;
saveData: (data: {
name: string;
value: number;
}) => Promise<{ success: boolean }>;
dom?: import("expo/dom").DOMProps;
}
export default function DOMComponent({ showAlert, saveData }: Props) {
const handleClick = async () => {
await showAlert("Hello from the webview!");
const result = await saveData({ name: "test", value: 42 });
console.log("Save result:", result);
};
return <button onClick={handleClick}>Trigger Native Action</button>;
}通过props传递异步函数,将原生功能暴露给DOM组件:
tsx
// app/index.tsx (原生)
import { Alert } from "react-native";
import DOMComponent from "@/components/dom-component";
export default function Screen() {
return (
<DOMComponent
showAlert={async (message: string) => {
Alert.alert("From Web", message);
}}
saveData={async (data: { name: string; value: number }) => {
// 保存到原生存储、数据库等
console.log("Saving:", data);
return { success: true };
}}
/>
);
}tsx
// components/dom-component.tsx
"use dom";
interface Props {
showAlert: (message: string) => Promise<void>;
saveData: (data: {
name: string;
value: number;
}) => Promise<{ success: boolean }>;
dom?: import("expo/dom").DOMProps;
}
export default function DOMComponent({ showAlert, saveData }: Props) {
const handleClick = async () => {
await showAlert("Hello from the webview!");
const result = await saveData({ name: "test", value: 42 });
console.log("Save result:", result);
};
return <button onClick={handleClick}>Trigger Native Action</button>;
}Using Web Libraries
使用Web库
DOM components can use any web library:
tsx
// components/syntax-highlight.tsx
"use dom";
import SyntaxHighlighter from "react-syntax-highlighter";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
interface Props {
code: string;
language: string;
dom?: import("expo/dom").DOMProps;
}
export default function SyntaxHighlight({ code, language }: Props) {
return (
<SyntaxHighlighter language={language} style={docco}>
{code}
</SyntaxHighlighter>
);
}tsx
// components/chart.tsx
"use dom";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
} from "recharts";
interface Props {
data: Array<{ name: string; value: number }>;
dom: import("expo/dom").DOMProps;
}
export default function Chart({ data }: Props) {
return (
<LineChart width={400} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
);
}DOM组件可以使用任何Web库:
tsx
// components/syntax-highlight.tsx
"use dom";
import SyntaxHighlighter from "react-syntax-highlighter";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
interface Props {
code: string;
language: string;
dom?: import("expo/dom").DOMProps;
}
export default function SyntaxHighlight({ code, language }: Props) {
return (
<SyntaxHighlighter language={language} style={docco}>
{code}
</SyntaxHighlighter>
);
}tsx
// components/chart.tsx
"use dom";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
} from "recharts";
interface Props {
data: Array<{ name: string; value: number }>;
dom: import("expo/dom").DOMProps;
}
export default function Chart({ data }: Props) {
return (
<LineChart width={400} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
);
}CSS in DOM Components
DOM组件中的CSS
CSS imports must be in the DOM component file since they run in isolated context:
tsx
// components/styled-component.tsx
"use dom";
import "@/styles.css"; // CSS file in same directory
export default function StyledComponent({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return (
<div className="container">
<h1 className="title">Styled Content</h1>
</div>
);
}Or use inline styles / CSS-in-JS:
tsx
"use dom";
const styles = {
container: {
padding: 20,
backgroundColor: "#f0f0f0",
},
title: {
fontSize: 24,
color: "#333",
},
};
export default function StyledComponent({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return (
<div style={styles.container}>
<h1 style={styles.title}>Styled Content</h1>
</div>
);
}CSS导入必须在DOM组件文件中,因为它们在隔离环境中运行:
tsx
// components/styled-component.tsx
"use dom";
import "@/styles.css"; // 同一目录下的CSS文件
export default function StyledComponent({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return (
<div className="container">
<h1 className="title">Styled Content</h1>
</div>
);
}或者使用内联样式/CSS-in-JS:
tsx
"use dom";
const styles = {
container: {
padding: 20,
backgroundColor: "#f0f0f0",
},
title: {
fontSize: 24,
color: "#333",
},
};
export default function StyledComponent({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return (
<div style={styles.container}>
<h1 style={styles.title}>Styled Content</h1>
</div>
);
}Expo Router in DOM Components
DOM组件中的Expo Router
The expo-router component and router API work inside DOM components:
<Link />tsx
"use dom";
import { Link, useRouter } from "expo-router";
export default function Navigation({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
const router = useRouter();
return (
<nav>
<Link href="/about">About</Link>
<button onClick={() => router.push("/settings")}>Settings</button>
</nav>
);
}expo-router的组件和路由API可在DOM组件中使用:
<Link />tsx
"use dom";
import { Link, useRouter } from "expo-router";
export default function Navigation({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
const router = useRouter();
return (
<nav>
<Link href="/about">About</Link>
<button onClick={() => router.push("/settings")}>Settings</button>
</nav>
);
}Router APIs That Require Props
需要Props的路由API
These hooks don't work directly in DOM components because they need synchronous access to native routing state:
useLocalSearchParams()useGlobalSearchParams()usePathname()useSegments()useRootNavigation()useRootNavigationState()
Solution: Read these values in the native parent and pass as props:
tsx
// app/[id].tsx (native)
import { useLocalSearchParams, usePathname } from "expo-router";
import DOMComponent from "@/components/dom-component";
export default function Screen() {
const { id } = useLocalSearchParams();
const pathname = usePathname();
return <DOMComponent id={id as string} pathname={pathname} />;
}tsx
// components/dom-component.tsx
"use dom";
interface Props {
id: string;
pathname: string;
dom?: import("expo/dom").DOMProps;
}
export default function DOMComponent({ id, pathname }: Props) {
return (
<div>
<p>Current ID: {id}</p>
<p>Current Path: {pathname}</p>
</div>
);
}以下钩子无法直接在DOM组件中使用,因为它们需要同步访问原生路由状态:
useLocalSearchParams()useGlobalSearchParams()usePathname()useSegments()useRootNavigation()useRootNavigationState()
解决方案: 在原生父组件中读取这些值,再通过props传递:
tsx
// app/[id].tsx (原生)
import { useLocalSearchParams, usePathname } from "expo-router";
import DOMComponent from "@/components/dom-component";
export default function Screen() {
const { id } = useLocalSearchParams();
const pathname = usePathname();
return <DOMComponent id={id as string} pathname={pathname} />;
}tsx
// components/dom-component.tsx
"use dom";
interface Props {
id: string;
pathname: string;
dom?: import("expo/dom").DOMProps;
}
export default function DOMComponent({ id, pathname }: Props) {
return (
<div>
<p>Current ID: {id}</p>
<p>Current Path: {pathname}</p>
</div>
);
}Detecting DOM Environment
检测DOM环境
Check if code is running in a DOM component:
tsx
"use dom";
import { IS_DOM } from "expo/dom";
export default function Component({
dom,
}: {
dom?: import("expo/dom").DOMProps;
}) {
return <div>{IS_DOM ? "Running in DOM component" : "Running natively"}</div>;
}检查代码是否在DOM组件中运行:
tsx
"use dom";
import { IS_DOM } from "expo/dom";
export default function Component({
dom,
}: {
dom?: import("expo/dom").DOMProps;
}) {
return <div>{IS_DOM ? "Running in DOM component" : "Running natively"}</div>;
}Assets
资源
Prefer requiring assets instead of using the public directory:
tsx
"use dom";
// Good - bundled with the component
const logo = require("../assets/logo.png");
export default function Component({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return <img src={logo} alt="Logo" />;
}优先使用require引入资源,而非公共目录:
tsx
"use dom";
// 推荐 - 与组件捆绑
const logo = require("../assets/logo.png");
export default function Component({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return <img src={logo} alt="Logo" />;
}Usage from Native Components
在原生组件中使用
Import and use DOM components like regular components:
tsx
// app/index.tsx
import { View, Text } from "react-native";
import WebChart from "@/components/web-chart";
import CodeBlock from "@/components/code-block";
export default function HomeScreen() {
return (
<View style={{ flex: 1 }}>
<Text>Native content above</Text>
<WebChart data={[10, 20, 30, 40, 50]} dom={{ style: { height: 300 } }} />
<CodeBlock
code="const x = 1;"
language="javascript"
dom={{ scrollEnabled: true }}
/>
<Text>Native content below</Text>
</View>
);
}像引入普通组件一样导入并使用DOM组件:
tsx
// app/index.tsx
import { View, Text } from "react-native";
import WebChart from "@/components/web-chart";
import CodeBlock from "@/components/code-block";
export default function HomeScreen() {
return (
<View style={{ flex: 1 }}>
<Text>Native content above</Text>
<WebChart data={[10, 20, 30, 40, 50]} dom={{ style: { height: 300 } }} />
<CodeBlock
code="const x = 1;"
language="javascript"
dom={{ scrollEnabled: true }}
/>
<Text>Native content below</Text>
</View>
);
}Platform Behavior
平台行为
| Platform | Behavior |
|---|---|
| iOS | Rendered in WKWebView |
| Android | Rendered in WebView |
| Web | Rendered as-is (no webview wrapper) |
On web, the prop is ignored since no webview is needed.
dom| 平台 | 行为 |
|---|---|
| iOS | 在WKWebView中渲染 |
| Android | 在WebView中渲染 |
| Web | 直接原样渲染(无webview包装) |
在Web平台上,属性会被忽略,因为不需要webview。
domTips
提示
- DOM components hot reload during development
- Keep DOM components focused — don't put entire screens in webviews
- Use native components for navigation chrome, DOM components for specialized content
- Test on all platforms — web rendering may differ slightly from native webviews
- Large DOM components may impact performance — profile if needed
- The webview has its own JavaScript context — cannot directly share state with native
- 开发过程中DOM组件支持热重载
- 保持DOM组件聚焦——不要将整个屏幕放入webview
- 导航栏使用原生组件,特殊内容使用DOM组件
- 在所有平台上测试——Web渲染可能与原生webview略有不同
- 大型DOM组件可能影响性能——必要时进行性能分析
- Webview有独立的JavaScript上下文——无法与原生直接共享状态