Qu'est-ce que les DOM Components ?
Les DOM components permettent à du code web de s'exécuter verbatim dans une webview sur les plateformes natives tout en s'affichant tel quel sur le web. Cela permet d'utiliser des bibliothèques web uniquement comme recharts, react-syntax-highlighter, ou n'importe quelle bibliothèque React web dans votre app Expo sans modification.
Quand utiliser les DOM Components
Utilisez les DOM components quand vous avez besoin de :
- Bibliothèques web uniquement — Graphiques (recharts, chart.js), surligneurs de syntaxe, éditeurs de texte enrichi, ou n'importe quelle bibliothèque dépendant des APIs DOM
- Migrer du code web — Apporter des composants React web existants vers native sans réécriture
- Mises en page HTML/CSS complexes — Quand les fonctionnalités CSS ne sont pas disponibles dans React Native
- iframes ou embeds — Intégrer du contenu externe qui nécessite un contexte navigateur
- Canvas ou WebGL — APIs graphiques web non disponibles nativement
Quand NE PAS utiliser les DOM Components
Évitez les DOM components quand :
- Les performances natives sont critiques — Les webviews ajoutent de la surcharge
- Interface simple — Les composants React Native sont plus efficaces pour les mises en page basiques
- Intégration native profonde — Utilisez les modules locaux à la place pour les APIs natives
- Routes de mise en page — Les fichiers
_layoutne peuvent pas être des DOM components
DOM Component de base
Créez un nouveau fichier avec la directive 'use dom'; en haut :
// 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>
);
}
Règles pour les DOM Components
- Doit avoir la directive
'use dom';en haut du fichier - Export par défaut unique — Un composant React par fichier
- Son propre fichier — Ne peut pas être défini inline ou combiné avec des composants natifs
- Props sérialisables uniquement — Chaînes, nombres, booléens, tableaux, objets bruts
- Inclure le CSS dans le fichier composant — Les DOM components s'exécutent dans un contexte isolé
La prop dom
Chaque DOM component reçoit une prop spéciale dom pour la configuration de la webview. Tapez-la toujours dans vos props :
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}
Options courantes de la prop dom
// Désactiver le défilement du body
<DOMComponent dom={{ scrollEnabled: false }} />
// Afficher sous l'enoche (désactiver les insets de zone sûre)
<DOMComponent dom={{ contentInsetAdjustmentBehavior: "never" }} />
// Contrôler la taille manuellement
<DOMComponent dom={{ style: { width: 300, height: 400 } }} />
// Combiner les options
<DOMComponent
dom={{
scrollEnabled: false,
contentInsetAdjustmentBehavior: "never",
style: { width: '100%', height: 500 }
}}
/>
Exposer les actions natives à la webview
Passez des fonctions async en tant que props pour exposer les fonctionnalités natives au 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 }) => {
// Enregistrer dans le stockage natif, la base de données, 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>;
}
Utiliser des bibliothèques web
Les DOM components peuvent utiliser n'importe quelle bibliothèque 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>
);
}
CSS dans les DOM Components
Les imports CSS doivent être dans le fichier du DOM component puisqu'ils s'exécutent dans un contexte isolé :
// components/styled-component.tsx
"use dom";
import "@/styles.css"; // Fichier CSS dans le même répertoire
export default function StyledComponent({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return (
<div className="container">
<h1 className="title">Styled Content</h1>
</div>
);
}
Ou utiliser des styles inline / 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 dans les DOM Components
Le composant <Link /> de expo-router et l'API router fonctionnent à l'intérieur des 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>
);
}
APIs Router qui nécessitent des props
Ces hooks ne fonctionnent pas directement dans les DOM components car ils ont besoin d'un accès synchrone à l'état de routage natif :
useLocalSearchParams()useGlobalSearchParams()usePathname()useSegments()useRootNavigation()useRootNavigationState()
Solution : Lisez ces valeurs dans le parent natif et passez-les en tant que 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>
);
}
Détecter l'environnement DOM
Vérifiez si le code s'exécute dans un 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>;
}
Ressources
Préférez importer les ressources plutôt que d'utiliser le répertoire public :
"use dom";
// Bon - regroupé avec le composant
const logo = require("../assets/logo.png");
export default function Component({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return <img src={logo} alt="Logo" />;
}
Utilisation depuis les composants natifs
Importez et utilisez les DOM components comme des composants ordinaires :
// 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>
);
}
Comportement par plateforme
| Plateforme | Comportement |
|---|---|
| iOS | Affiché dans WKWebView |
| Android | Affiché dans WebView |
| Web | Affiché tel quel (sans wrapper webview) |
Sur le web, la prop dom est ignorée puisqu'aucune webview n'est nécessaire.
Conseils
- Les DOM components se rechargent à chaud lors du développement
- Gardez les DOM components ciblés — ne mettez pas des écrans entiers dans les webviews
- Utilisez les composants natifs pour la navigation, les DOM components pour le contenu spécialisé
- Testez sur toutes les plateformes — le rendu web peut différer légèrement des webviews natives
- Les grands DOM components peuvent impacter les performances — profilez si nécessaire
- La webview a son propre contexte JavaScript — ne peut pas partager directement l'état avec le natif