Expo DOM コンポーネント(ウェブ画面用の部品)を使って、ウェブコードをネイティブアプリ(スマートフォン用アプリ)のウェブビュー(アプリ内ブラウザ)で実行したり、ウェブ上ではそのまま動作させたりできます。ウェブコードをネイティブアプリへ段階的に移行できます。
Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally.
DOMコンポーネントを使うと、Webコードをネイティブプラットフォーム上のwebviewでそのまま実行しつつ、Web上ではそのままレンダリングできます。
これにより、recharts・react-syntax-highlighterなどのWebのみ対応ライブラリや、あらゆるReact Webライブラリを、修正なしにExpoアプリで利用できます。
次のような場合に使用:
次のような場合は避けてください:
_layoutファイルはDOMコンポーネントにできませんファイル先頭に'use dom';ディレクティブを付けて新しいファイルを作成します:
// 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';ディレクティブが必須dom propすべてのDOMコンポーネントは、webviewの設定用に特別なdom propを受け取ります。
propsの型定義に必ず含めてください:
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}
dom propのよく使うオプション// ボディのスクロールを無効化
<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 }
}}
/>
非同期関数をpropsとして渡すことで、DOMコンポーネントからネイティブ機能を呼び出せます:
// 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 };
}}
/>
);
}
// 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>;
}
DOMコンポーネントでは、あらゆるWebライブラリを使用できます:
// 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>
);
}
// 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コンポーネントは隔離されたコンテキストで動作するため、CSSのimportはDOMコンポーネントファイル内に記述する必要があります:
// 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を使うこともできます:
"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の<Link />コンポーネントとrouter APIはDOMコンポーネント内でも動作します:
"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>
);
}
以下のhookは、ネイティブのルーティング状態への同期アクセスが必要なため、DOMコンポーネント内では直接動作しません:
useLocalSearchParams()useGlobalSearchParams()usePathname()useSegments()useRootNavigation()useRootNavigationState()解決策: ネイティブの親コンポーネントでこれらの値を取得し、propsとして渡します:
// 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} />;
}
// 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コンポーネント内で動作しているかどうかを確認できます:
"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>;
}
publicディレクトリではなく、requireでアセットを読み込むことを推奨します:
"use dom";
// 推奨 — コンポーネントと一緒にバンドルされる
const logo = require("../assets/logo.png");
export default function Component({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return <img src={logo} alt="Logo" />;
}
DOMコンポーネントは通常のコンポーネントと同様にimportして使用できます:
// 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>
);
}
| プラットフォーム | 動作 |
|---|---|
| iOS | WKWebViewでレンダリング |
| Android | WebViewでレンダリング |
| Web | そのままレンダリング(webviewラッパーなし) |
Web上ではdom propは無視されます(webviewが不要なため)。
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 recharts, react-syntax-highlighter, or any React web library in your Expo app without modification.
Use DOM components when you need:
Avoid DOM components when:
_layout files cannot be DOM componentsCreate a new file with the 'use dom'; directive at the top:
// 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'; directive at the top of the filedom PropEvery DOM component receives a special dom prop for webview configuration. Always type it in your props:
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}
dom Prop Options// 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 }
}}
/>
Pass async functions as props to expose native functionality to the DOM component:
// 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 };
}}
/>
);
}
// 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>;
}
DOM components can use any web library:
// 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>
);
}
// 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 imports must be in the DOM component file since they run in isolated context:
// 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:
"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>
);
}
The expo-router <Link /> component and router API work inside DOM components:
"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>
);
}
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:
// 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} />;
}
// 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>
);
}
Check if code is running in a DOM component:
"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>;
}
Prefer requiring assets instead of using the public directory:
"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" />;
}
Import and use DOM components like regular components:
// 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 |
|---|---|
| iOS | Rendered in WKWebView |
| Android | Rendered in WebView |
| Web | Rendered as-is (no webview wrapper) |
On web, the dom prop is ignored since no webview is needed.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。