Chapitre 3.1 — Il n’y a pas de div
Où on en est : Drill affiche son premier écran, mais vous l’avez écrit en copiant. Ce chapitre installe le mental model qui rend tous les suivants naturels.
⏱️ TL;DR — En RN, le JSX ne produit pas du HTML mais des widgets natifs. Il n’existe qu’une poignée de primitives (
View,Text,Image,TextInput,ScrollView…), et leurs règles sont plus strictes que le web : tout texte doit vivre dans un<Text>, uneViewne scroll pas, il n’y a ni cascade de styles ni balises sémantiques. Moins de primitives = moins à apprendre — mais les règles sont non négociables.
🎯 Objectifs
- Mémoriser la table de correspondance HTML → primitives RN.
- Intégrer les trois règles strictes (texte, scroll, styles) et leurs messages d’erreur.
- Comprendre l’imbrication de
Textet ce qu’elle remplace (span,strong). - Savoir ce qui N’existe PAS (et par quoi on le remplace).
La table de correspondance
Votre dictionnaire web → natif — celui qu’on affine en annexe (Partie 16) :
| Web | React Native | Nuances |
|---|---|---|
div, section, header… | View | UNE seule primitive de bloc |
span, p, h1-h6, strong | Text | La taille fait le titre, pas la balise |
img | Image (ou expo-image) | source, pas src — chapitre 3.2 |
button, a | Pressable | Pas de navigation intégrée — chapitre 3.3 |
input | TextInput | chapitre 3.5 |
ul + overflow | ScrollView / FlatList | Le scroll est un COMPOSANT — chapitre 3.4 |
form | — | N’existe pas : état contrôlé + bouton |
table | — | N’existe pas : Views en flexbox |
Notez la brièveté : RN expose ~10 primitives là où HTML en a ~110. Tout le reste — cartes, badges, boutons de réponse de Drill — se compose à partir d’elles.
Règle stricte n°1 : le texte vit dans Text
// ❌ Crash : "Text strings must be rendered within a <Text> component"
<View>Bonjour</View>
// ✅
<View><Text>Bonjour</Text></View>Sur le web, n’importe quel élément peut contenir du texte. En natif, non : Android sait
afficher du texte dans un TextView, point. Ce message d’erreur — apprenez-le par cœur — est
LE plus fréquent de tout débutant. Il surgit aussi de façon sournoise :
// ❌ Crash sournois : si count vaut 0, `0 && …` vaut… 0, un texte nu dans la View !
<View>{count && <Badge n={count} />}</View>
// ✅ Toujours un booléen à gauche d'un && dans le JSX RN
<View>{count > 0 && <Badge n={count} />}</View>⚠️ Piège — Ce
0piégeux existe aussi sur le web (React affiche « 0 »), mais en RN il crash. Réflexe définitif : dans du JSX RN, l’opérande gauche de&&doit être un booléen (count > 0,!!items.length).
Règle stricte n°2 : une View ne scroll pas
Sur le web, overflow: auto et tout défile. En RN, une View dont le contenu dépasse…
coupe le contenu, silencieusement. Le défilement est un composant à part entière :
ScrollView (contenu court) ou FlatList (listes — chapitre 3.4). On y revient au chapitre
3.6 pour choisir.
Règle stricte n°3 : pas de cascade
En CSS, body { font-family } ruisselle sur tout. En RN, les styles ne s’héritent pas
entre composants — chaque Text repart de zéro (exception : un Text imbriqué dans un
Text hérite de son parent). Conséquence pratique : on crée très tôt ses composants de texte
(<AppText>) qui portent la police et la couleur du thème — c’est le chantier de la Partie 4.
L’imbrication de Text — votre nouveau span
Comment faire du texte riche sans <strong> ni <em> ? En imbriquant :
// components/QuestionPrompt.tsx — aperçu Drill
<Text style={styles.prompt}>
En Symfony, le composant <Text style={styles.code}>Routing</Text> associe une
URL à un <Text style={styles.bold}>contrôleur</Text>.
</Text>Les Text internes héritent du style du parent et le surchargent — exactement le rôle du
span. Bonus : tout le bloc se wrappe comme UN paragraphe (des Text frères, eux, seraient
des blocs séparés).
Ce qui n’existe pas (et les bons réflexes)
- Pas de
window/document/ DOM.window.location→ Expo Router ;localStorage→ AsyncStorage (Partie 6) ;document.querySelector→ jamais (refs React si besoin). - Pas de sémantique HTML (nav, article, aria-*) → l’accessibilité passe par des props
dédiées :
accessibilityRole="button",accessibilityLabel(chapitre 3.3). - Pas de
<a href>→<Link>d’Expo Router pour l’interne (Partie 5),Linking.openURLpour l’externe. - Pas de curseur, pas de hover → les états tactiles de
Pressable(chapitre 3.3). - Pas d’unités CSS (
px,rem,%partiel) → nombres sans unité = dp, des pixels indépendants de la densité (Partie 4).
💡 Pour un dev Next.js — La bonne nouvelle cachée : cette pauvreté de primitives est une libération. Plus de débat div/section/article, plus de reset CSS, plus de spécificité. Un design system RN, c’est ~10 composants maison bien stylés — Drill construira le sien en Partie 4, et vous le recopierez dans toutes vos apps perso.
✏️ Exercices
1. Corrigez ce composant qui contient trois violations des règles du chapitre :
function ScoreCard({ score }: { score: number }) {
return (
<View>
Score actuel
<View>{score && <Text>{score} pts</Text>}</View>
<p style={{ fontStyle: 'italic' }}>Continue comme ça !</p>
</View>
)
}✅ Solution
function ScoreCard({ score }: { score: number }) {
return (
<View>
<Text>Score actuel</Text>
<View>{score > 0 && <Text>{score} pts</Text>}</View>
<Text style={{ fontStyle: 'italic' }}>Continue comme ça !</Text>
</View>
)
}(1) « Score actuel » était du texte nu dans une View → crash. (2) score && … avec un nombre :
si 0, texte nu → crash sournois ; on passe par score > 0. (3) <p> n’existe pas → Text.
2. Dans Drill, écrivez le composant StreakLabel qui affiche « Série : 7 🔥 » avec le
chiffre en gras et plus grand, le tout se wrappant comme un seul paragraphe.
✅ Solution
// components/StreakLabel.tsx
import { StyleSheet, Text } from 'react-native'
export function StreakLabel({ streak }: { streak: number }) {
return (
<Text style={styles.base}>
Série : <Text style={styles.value}>{streak}</Text> 🔥
</Text>
)
}
const styles = StyleSheet.create({
base: { fontSize: 16, color: '#e2e8f0' },
value: { fontSize: 20, fontWeight: '800', color: '#58c4dc' },
})L’imbrication Text > Text donne l’héritage + surcharge, et le wrapping en un seul bloc.
3. De mémoire : par quoi remplace-t-on localStorage, <a href>, overflow: auto, et
onClick ?
✅ Solution
localStorage → AsyncStorage (Partie 6). <a href> → Link (Expo Router) ou
Linking.openURL pour l’externe. overflow: auto → ScrollView/FlatList (le scroll est un
composant). onClick → onPress sur un Pressable.
🧠 Quiz
1. Quel message d’erreur obtient-on en posant du texte directement dans une View ?
Réponse
« Text strings must be rendered within a <Text> component » — l’erreur la plus fréquente
du débutant venu du web.
2. Pourquoi {count && <X/>} est-il dangereux en RN et quelle est la forme sûre ?
Réponse
Si count vaut 0, l’expression vaut 0 → texte nu rendu dans une View → crash. Forme sûre :
{count > 0 && <X/>} (opérande gauche booléen).
3. Comment obtenir l’équivalent d’un <strong> au milieu d’une phrase ?
Réponse
Un Text imbriqué dans le Text parent avec un style plus fort (fontWeight: '700') : il
hérite du reste et le tout se wrappe comme un seul paragraphe.
4. Que se passe-t-il quand le contenu d’une View dépasse sa taille ?
Réponse
Il est coupé (ou déborde visuellement selon overflow), mais ne scroll JAMAIS tout seul :
le défilement exige un composant dédié (ScrollView/FlatList).
5. Les styles se cascadent-ils en RN ? Citez l’exception.
Réponse
Non — aucun héritage entre composants ; chaque Text repart de zéro. Exception : un Text imbriqué dans un Text hérite des styles de texte de son parent.
👉 Chapitre suivant : 3.2 — View, Text, Image — les trois piliers en détail.