Dans cette série d'articles, nous allons créer notre propre implémentation React à partir de zéro. À la fin, vous comprendrez comment fonctionne React, quelles méthodes du cycle de vie des composants qu'il appelle et pourquoi. L'article est destiné à ceux qui ont déjà utilisé React et qui veulent en savoir plus sur son appareil, ou aux très curieux.

Cet article est une traduction de
React Internals, première partie: rendu de baseCeci est en fait le premier article sur cinq
- Notions de base sur le rendu <- nous sommes ici
- ComponentWillMount et componentDidMount
- Mettre à jour
- setState
- Les transactions
Le matériel a été créé lorsque React 15.3 était pertinent, en particulier l'utilisation de ReactDOM et du réconciliateur de pile. React 16 et supérieur a quelques changements. Cependant, ce matériel reste pertinent, car il donne une idée générale de ce qui se passe «sous le capot».
Partie 1. Bases du rendu
Éléments et composants
Il existe trois types d'entités dans React: un élément DOM natif, un élément React virtuel et un composant.
Éléments DOM natifs
Ce sont les éléments DOM que le navigateur utilise pour créer la page Web, par exemple, div, span, h1. React les crée en appelant document.createElement () et interagit avec la page à l'aide de méthodes API DOM basées sur navigateur telles que element.insertBefore (), element.nodeValue et autres.
Élément de réaction virtuelle
Un élément React virtuel (souvent appelé simplement «élément») est un objet javascript qui contient les propriétés nécessaires pour créer ou mettre à jour un élément DOM natif ou une arborescence de ces éléments. Sur la base de l'élément React virtuel, des éléments DOM natifs sont créés, tels que div, span, h1 et autres. Nous pouvons dire qu'un élément React virtuel est une instance d'un composant composite défini par l'utilisateur, plus à ce sujet ci-dessous.
Composant
Composant est un terme assez général dans React. Les composants sont des entités avec lesquelles React effectue diverses manipulations. Différents composants ont des objectifs différents. Par exemple, le ReactDomComponent de la bibliothèque ReactDom est responsable de la liaison entre les éléments React et leurs éléments DOM natifs correspondants.
Composants composés personnalisés
Vous avez probablement déjà rencontré ce type de composant. Lorsque vous appelez React.createClass () ou utilisez des classes ES6 via l'extension React.Component, vous créez un composant composite personnalisé. Un tel composant a des méthodes de cycle de vie, telles que componentWillMount, shouldComponentUpdate et autres. Nous pouvons les redéfinir pour ajouter une sorte de logique. De plus, d'autres méthodes sont créées, telles que mountComponent, receiveComponent. Ces méthodes sont utilisées uniquement par React à des fins internes; nous n'interagissons en aucune manière avec elles.
ZanudaMode = onEn fait, les composants créés par l'utilisateur ne sont pas initialement terminés. React les enveloppe dans un ReactCompositeComponentWrapper, qui ajoute toutes les méthodes de cycle de vie à nos composants, après quoi React peut les gérer (insertion, mise à jour, etc.).
Réagir déclaratif
En ce qui concerne les composants personnalisés, notre tâche consiste à définir les classes de ces composants, mais nous n'instancions pas ces classes. React les crée en cas de besoin.
De plus, nous ne créons pas explicitement d'éléments en utilisant un style impératif; au lieu de cela, nous écrivons dans un style déclaratif en utilisant JSX:
class MyComponent extends React.Component { render() { return <div>hello</div>; } }
Ce code avec le balisage JSX est traduit par le compilateur comme suit:
class MyComponent extends React.Component { render() { return React.createElement('div', null, 'hello'); } }
Autrement dit, il se transforme en une construction impérative pour créer un élément via un appel explicite à React.createElement (). Mais cette construction est à l'intérieur de la méthode render (), que nous n'appelons pas explicitement, React appellera cette méthode elle-même si nécessaire. Par conséquent, percevoir React est tout aussi déclaratif: nous décrivons ce que nous voulons recevoir et React détermine comment le faire.
Écrivez votre petit React
Après avoir reçu la base technique nécessaire, nous allons commencer à créer notre propre implémentation React. Ce sera une version très simplifiée, appelons-la Feact.
Supposons que nous voulons créer une application Feact simple dont le code ressemblerait à ceci:
Feact.render(<h1>hello world</h1>, document.getElementById('root'));
Tout d'abord, parlons de JSX. Il s'agit précisément d'une «retraite», car l'analyse JSX est un grand sujet distinct que nous allons omettre dans le cadre de notre implémentation de Feact. Si nous avions affaire à JSX traité, nous verrions le code suivant:
Feact.render( Feact.createElement('h1', null, 'hello world'), document.getElementById('root') );
Autrement dit, nous utilisons Feact.createElement au lieu de JSX. Nous implémentons donc cette méthode:
const Feact = { createElement(type, props, children) { const element = { type, props: props || {} }; if (children) { element.props.children = children; } return element; } };
L'élément retourné est un objet simple qui représente ce que nous voulons rendre.
Que fait Feact.render ()?
En appelant Feact.render (), nous passons deux paramètres: ce que nous voulons rendre et où. Il s'agit du point de départ de toute application React. Écrivons une implémentation de la méthode render () pour Feact:
const Feact = { createElement() { }, render(element, container) { const componentInstance = new FeactDOMComponent(element); return componentInstance.mountComponent(container); } };
À la fin de render (), nous obtenons une page Web terminée. Les éléments DOM sont créés par FeactDOMComponent. Écrivons son implémentation:
class FeactDOMComponent { constructor(element) { this._currentElement = element; } mountComponent(container) { const domElement = document.createElement(this._currentElement.type); const text = this._currentElement.props.children; const textNode = document.createTextNode(text); domElement.appendChild(textNode); container.appendChild(domElement); this._hostNode = domElement; return domElement; } }
La méthode mountComponent crée un élément DOM et le stocke dans this._hostNode. Nous ne l'utiliserons pas maintenant, mais nous y reviendrons dans les parties suivantes.
La version actuelle de l'application peut être consultée au
violon .
Littéralement, 40 lignes de code suffisaient pour effectuer une implémentation primitive de React. Le Feact que nous avons créé a peu de chances de conquérir le monde, mais il reflète bien l'essence de ce qui se passe sous le capot de React.
Ajout de composants personnalisés
Notre Feact devrait être capable de rendre non seulement des éléments en HTML (div, span, etc.), mais aussi des composants composites définis par l'utilisateur:
La méthode Feact.createElement () décrite plus haut est actuellement correcte, donc je ne la répéterai pas dans la liste de code.
const Feact = { createClass(spec) { function Constructor(props) { this.props = props; } Constructor.prototype.render = spec.render; return Constructor; }, render(element, container) {
Permettez-moi de vous rappeler que si JSX était disponible, l'appel de la méthode render () ressemblerait à ceci:
Feact.render( <MyTitle message="hey there Feact" />, document.getElementById('root') );
Nous avons transmis la classe de composants personnalisés à createElement. Un élément React virtuel peut représenter soit un élément DOM normal soit un composant personnalisé. Nous les distinguerons comme suit: si nous passons un type de chaîne, alors c'est un élément DOM; s'il s'agit d'une fonction, alors cet élément représente un composant personnalisé.
Amélioration de Feact.render ()
Si vous regardez attentivement le code pour le moment, vous verrez que Feact.render () ne peut pas traiter les composants personnalisés. Corrigeons ceci:
Feact = { render(element, container) { const componentInstance = new FeactCompositeComponentWrapper(element); return componentInstance.mountComponent(container); } } class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); const element = componentInstance.render(); const domComponentInstance = new FeactDOMComponent(element); return domComponentInstance.mountComponent(container); } }
Nous avons créé un wrapper pour l'élément transmis. À l'intérieur de l'encapsuleur, nous créons une instance de la classe de composant utilisateur et appelons sa méthode componentInstance.render (). Le résultat de cette méthode peut être transmis au FeactDOMComponent, où les éléments DOM correspondants seront créés.
Nous pouvons maintenant créer et rendre des composants personnalisés. Feact créera des nœuds DOM basés sur des composants personnalisés et les modifiera en fonction des propriétés (accessoires) de nos composants personnalisés. Il s'agit d'une amélioration significative de notre Feact.
Notez que le FeactCompositeComponentWrapper crée directement le FeactDOMComponent. Une relation aussi étroite est mauvaise. Nous corrigerons cela plus tard. Si React avait la même connexion étroite, seules les applications Web pouvaient être créées. L'ajout d'une couche supplémentaire ReactCompositeComponentWrapper vous permet de séparer la logique React pour la gestion des éléments virtuels et l'affichage final des éléments natifs, ce qui vous permet d'utiliser React non seulement lors de la création d'applications Web, mais aussi, par exemple, React Native pour mobile.
Amélioration des composants personnalisés
Les composants personnalisés créés ne peuvent renvoyer que des éléments DOM natifs, si nous essayons de renvoyer d'autres composants personnalisés, nous obtenons une erreur. Corrigez cette faille. Imaginez que nous aimerions exécuter le code suivant sans erreur:
const MyMessage = Feact.createClass({ render() { if (this.props.asTitle) { return Feact.createElement(MyTitle, { message: this.props.message }); } else { return Feact.createElement('p', null, this.props.message); } } }
La méthode render () d'un composant personnalisé peut renvoyer un élément DOM natif ou un autre composant personnalisé. Si la propriété asTitle est vraie, le FeactCompositeComponentWrapper renverra le composant personnalisé pour le FeactDOMComponent où l'erreur se produira. Correction de FeactCompositeComponentWrapper:
class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); let element = componentInstance.render(); while (typeof element.type === 'function') { element = (new element.type(element.props)).render(); } const domComponentInstance = new FeactDOMComponent(element); domComponentInstance.mountComponent(container); } }
En vérité, nous avons maintenant fabriqué une béquille pour répondre aux besoins actuels. Un appel à la méthode de rendu retournera des composants enfants jusqu'à ce qu'il retourne un élément DOM natif. C'est mauvais car ces composants enfants ne participeront pas au cycle de vie. Par exemple, dans ce cas, nous ne pourrons pas implémenter l'appel componentWillMount. Nous corrigerons cela plus tard.
Et encore une fois, nous corrigeons Feact.render ()
La première version de Feact.render () ne pouvait traiter que les éléments DOM natifs. Désormais, seuls les composants définis par l'utilisateur sont correctement traités sans prise en charge native. Il est nécessaire de gérer les deux cas. Vous pouvez écrire une fabrique qui créera un composant en fonction du type d'élément transmis, mais React a choisi une manière différente: il suffit d'envelopper tout composant entrant dans un autre composant:
const TopLevelWrapper = function(props) { this.props = props; }; TopLevelWrapper.prototype.render = function() { return this.props; }; const Feact = { render(element, container) { const wrapperElement = this.createElement(TopLevelWrapper, element); const componentInstance = new FeactCompositeComponentWrapper(wrapperElement);
TopLevelWrapper est essentiellement un composant personnalisé. Il peut également être défini en appelant Feact.createClass (). Sa méthode de rendu renvoie simplement l'élément qui lui est transmis. Désormais, chaque élément est encapsulé dans TopLevelWrapper et FeactCompositeComponentWrapper recevra toujours un composant personnalisé en entrée.
Conclusion de la première partie
Nous avons implémenté Feact, qui peut rendre les composants. Le code généré montre les concepts de base du rendu. Le rendu réel dans React est beaucoup plus compliqué et couvre les événements, la mise au point, le défilement des fenêtres, les performances, etc.
Le dernier
jsfiddle de la première partie.