Skip to Content

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>, une View ne 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 Text et 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) :

WebReact NativeNuances
div, section, headerViewUNE seule primitive de bloc
span, p, h1-h6, strongTextLa taille fait le titre, pas la balise
imgImage (ou expo-image)source, pas src — chapitre 3.2
button, aPressablePas de navigation intégrée — chapitre 3.3
inputTextInputchapitre 3.5
ul + overflowScrollView / FlatListLe scroll est un COMPOSANT — chapitre 3.4
formN’existe pas : état contrôlé + bouton
tableN’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 0 pié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.openURL pour 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

localStorageAsyncStorage (Partie 6). <a href>Link (Expo Router) ou Linking.openURL pour l’externe. overflow: autoScrollView/FlatList (le scroll est un composant). onClickonPress 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.