Préface
Cet article combinera les idées de conception de React pour réaliser le rendu des éléments, c'est-à-dire JSX语法
comment créer un vrai DOM et le rendre sur la page. Cet article n'implique fondamentalement pas le code source de React, mais il est cohérent avec le Idée d'implémentation de React, il convient donc très bien aux petits enfants. L'apprentissage est gratuit, il est recommandé de suivre les étapes pour taper le code, s'il y a une erreur, veuillez critiquer et corriger !
suggestion:
- Si vous ne savez pas ce qu'est JSX ou ne comprenez pas React, il est recommandé d'accéder d'abord à la documentation officielle de React et de suivre la documentation pour créer un jeu afin d'avoir une compréhension générale de JSX.
- Si vous souhaitez également apprendre le code source de Vue, vous pouvez également lire ce blog. Il est également cohérent avec l'idée d'implémentation de Vue, qui convertit le DOM virtuel en DOM réel.
- Ne vous emmêlez pas trop dans la manière dont chaque méthode est implémentée. Si vous vous emmêlez trop, vous tomberez dans l’enfer d’une boucle récursive infinie. Il en va de même pour le code source de React.
Essayons d'abord de créer un projet React :
npx create-react-app mon-application
Idées de mise en œuvre
Ici, nous discutons uniquement des principes de mise en œuvre du rendu des éléments
React utilise Babel pour traduire les fichiers de syntaxe JSX en fonction React.createElement, appelle la fonction React.createElement pour convertir JSX en un Dom virtuel (c'est-à-dire un objet Vnode), puis utilise la fonction ReactDOM.render pour transformer le DOM virtuel. dans un véritable montage DOM.
- Implémenter la fonction React.createElement
- Implémenter la fonction Render
- Rendu complet et affichage sur la page
Initialiser le projet
Lorsque vous créez un projet React via la méthode ci-dessus, vous pouvez également supprimer d'abord les fichiers redondants et les transformer en fichier jsx le plus simple.
Ici, je ne garde qu'un seul fichier.
import React from 'react';
import ReactDOM from 'react-dom/client';
let element = <h1>Hello, world</h1>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
element
);
Si vous imprimez avec succès un Hello, world, alors la première étape est réussie
React.createElement
La traduction de Babel implique la connaissance des arbres de syntaxe AST. Vous pouvez lire mon blog précédent. Je n'entrerai pas dans les détails ici. Ici, nous parlerons directement des étapes d'implémentation de Babel, convertissant les fichiers de syntaxe jsx en appels de fonction React.createElement et générant un DOM virtuel .
Structure des données du Dom virtuel
Ici, nous examinons d'abord la structure de données du Dom virtuel généré par React.createElement.Cela nous est utile pour créer un Dom virtuel par écriture manuscrite.
Nous imprimons directement l'élément Dom virtuel
import React from 'react';
import ReactDOM from 'react-dom/client';
let element = <h1>Hello, world</h1>;
console.log(element);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
element
);
Comme vous pouvez le voir, son essence est un objet. Babel le traduit dans la fonction createElement. Après l'avoir appelé, un objet est renvoyé. Cet objet est le Dom virtuel, qui contient plusieurs valeurs clés.
Autrement dit, cela devient un appel à cette fonction
React.createElement("h1",{
className:"title",style:{
color:'red'}},"hello")
Cette fonction accepte trois paramètres,
- L'un est le type d'élément
- La seconde est la configuration des éléments
- Le troisième est le contenu de l'élément (peut-être plus que du texte, il peut aussi s'agir d'un nœud d'élément)
clé valeur clé
- clé : utilisée par React pour implémenter l'algorithme de comparaison
- ref : utilisé pour obtenir le vrai Dom
- type : type d'élément
- props : configuration des éléments (tels que les nœuds enfants, les styles)
- $$typeof : l'identifiant unique de l'élément
Mise en œuvre
Comme mentionné précédemment, cette méthode accepte trois paramètres
- L'un est le type d'élément
- La seconde est la configuration des éléments
- Le troisième est le contenu de l'élément (peut-être plus que du texte, il peut aussi s'agir d'un nœud d'élément)
import React from 'react';
import ReactDOM from 'react-dom';
let element2 = React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, 'hello world','hi');
console.log(element2);
ReactDOM.render(
element2,
document.getElementById('root')
);
Note 1 : Si vous essayez d'ajouter un autre texte 'salut' après 'hello world', vous constaterez que 子节点有多个
l'attribut children dans ses accessoires sera 从一个字符串类型变成数组类型
. C'est très important !
Note 2 : Si vous n'êtes pas un texte, mais un objet élément, c'est un objet. S'il s'agit de plusieurs objets éléments, il devient un tableau avec des objets éléments à l'intérieur.
import React from 'react';
import ReactDOM from 'react-dom';
let element2 = React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, React.createElement("span", null, "hello"));
console.log(element2);
ReactDOM.render(
element2,
document.getElementById('root')
);
fonction d'initialisation
Nous créons un nouveau fichier React.js et exposons cet objet React. Il contient une fonction createElement. Nous allons utiliser cette fonction pour renvoyer un dom virtuel.
//接受三个参数,元素的类型、元素的配置、元素的节点
function createElement(type,config,children) {
//返回一个虚拟dom
return {
}
}
const React = {
createElement
}
export default React;
Gestion des clés et des références
Notre clé et notre référence sont toutes deux écrites dans la configuration, nous devons donc extraire la clé et la valeur séparément et les supprimer de la configuration.
//第一步,处理key和ref
let key, ref
if (config) {
key = config.key || null
ref = config.ref || null
delete config.key
delete config.ref
}
Manipulation des accessoires et des enfants
Nous avons découvert grâce au code source qu'il avait placé l'attribut children et tous les éléments de la configuration dans l'attribut props.
La deuxième étape consiste à mettre tous les éléments de la configuration dans des accessoires
let props = {
...config}
La troisième étape consiste à gérer le nœud enfants, il y a trois situations ici
- pas de nœud enfant
- a un nœud enfant - nœud de texte/nœud d'élément
- Il existe plusieurs nœuds enfants
//第二步,处理children
if (props) {
//有多个儿子
if (arguments.length > 3) {
//多个儿子,就把他们变成一个数组
props.children = Array.prototype.slice.call(arguments, 2)
//有一个儿子 (1)文本 (2)元素
}else if(arguments.length === 3){
props.children = children;
}
//没有儿子,不需要去处理
}
``
Gérer $$typeof
Cette clé est utilisée par React pour identifier les éléments. Nous créons un stant.js
fichier pour exposer tous les types d'identification.
//用于标识元素
export const REACT_ELEMENT = Symbol('react.element')
export const REACT_TEXT = Symbol('react.text')
optimisation
Lors du traitement du nœud enfant, lorsque nous n'avons qu'un seul nœud enfant et qu'il s'agit d'un texte, il est de type chaîne. Nous le traitons uniformément en type objet pour faciliter les opérations de mise à jour ultérieures, via la méthode toObject
import {
REACT_TEXT } from "./stants";
export function toObject(element) {
return typeof element === 'string' || typeof element === 'number' ? {
type:REACT_TEXT,content:element} : element
}
code global
réagir.js
//实现以下:
// let element2 = React.createElement("h1", {
// className: "title",
// style: {
// color: 'red'
// }
// }, React.createElement("span", null, "hello"));
import {
REACT_ELEMENT } from "./stants"
import {
toObject } from "./utils"
function createElement(type,config,children) {
if (config == null) {
config = {
}
}
//第一步,处理key和ref
let key, ref
if (config) {
key = config.key || null
ref = config.ref || null
delete config.key
delete config.ref
}
// 第二步,就是将config中的所有元素都放入到props中
let props = {
...config}
//第三步,处理children
if (props) {
//有多个儿子
if (arguments.length > 3) {
//多个儿子,就把他们变成一个数组
props.children = Array.prototype.slice.call(arguments, 2).map(toObject)
//有一个儿子 (1)文本 (2)元素
}else if(arguments.length === 3){
props.children = toObject(children) ; //统一转变成对象
}
//没有儿子,不需要去处理
}
//返回一个虚拟dom
return {
//vnode
key,
ref,
$$typeof:REACT_ELEMENT,
props,
type: type,
}
}
const React = {
createElement
}
export default React;
Essayons-le en introduisant notre propre fichier de réaction dans index.js. À ce stade, nous avons implémenté la fonction React.createElement et généré un Dom virtuel.
Fonction React.render
Cette fonction est la fonction clé pour convertir le dom virtuel en dom réel. Ici, nous acceptons deux paramètres, l'un est le dom virtuel et le second est le nœud de montage, qui doit implémenter cette fonction.
ReactDOM.render(
element2,
document.getElementById('root')
);
fonction d'initialisation
//将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
let dom //真实dom
return dom
}
function render(vnode, container) {
//将虚拟dom转变成真实dom
let dom = createDOM(vnode)
//将真实dom挂载到container上
container.appendChild(dom)
}
const ReactDOM = {
render
}
export default ReactDOM;
Type de processus et génération des nœuds d'éléments correspondants
Veuillez revenir sur la structure du nœud virtuel que nous avons généré
- clé : utilisée par React pour implémenter l'algorithme de comparaison
- ref : utilisé pour obtenir le vrai Dom
- type : type d'élément
- props : configuration des éléments (tels que les nœuds enfants, les styles)
- $$typeof : l'identifiant unique de l'élément
Nous avons effectué une optimisation ci-dessus : s'il s'agit de texte, nous le traitons nous-mêmes en une structure de données objet.
{
type:REACT_TEXT,
content:element
}
//将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
let {
type, props, content } = vnode
let Ndom;
//1、判断type是什么类型的,是文本还是元素并生成对应的节点
if (type === REACT_TEXT) {
//如果是一个文本类型的
Ndom = document.createTextNode(content) //注意:我们在前面已经把所有的文件节点处理为一个对象类型的了
} else {
Ndom = document.createElement(type) //div
}
//2、处理属性 {
children style:{
color:red,fontsize:16px} className="title" }
if (props) {
console.log("props",props)
//为了后续处理更新操作
updateProps(Ndom, {
}, props)
}
//3、处理子节点
return Ndom
}
Gestion des attributs
//初始化和更新props的方法
function updateProps(dom, oldProps, newProps) {
//初始化
if (newProps) {
//遍历新的属性对象
for (let key in newProps) {
if (key === 'children') {
continue
} else if (key === 'style') {
//如果是style的话就一个个追加进去
let styleObj = newProps[key]
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else {
//例如className就直接放上去即可
dom[key] = newProps[key]
}
}
}
//更新操作,如果有旧节点
if (oldProps) {
//旧的属性在新的属性中没有,则删除
for (let key in oldProps) {
if(!newProps[key]){
dom[key] = null
}
}
}
//2、处理属性 {
children style:{
color:red,fontsize:16px} className="title" }
if (props) {
//为了后续处理更新操作
updateProps(dom, {
}, props)
}
Traiter les nœuds enfants
//处理子节点
//接收两个参数,一个是子节点,另一个是挂载节点
function changeChildren(children, dom) {
//有一个儿子的情况 对象
if (typeof children == 'object'&& children.type ) {
render(children, dom) //递归调用
//有多个儿子的情况 数组
} else if (Array.isArray(children)) {
//循环处理
children.forEach(child =>
render(child, dom)
)
}
}
code global
import {
REACT_TEXT } from "./stants"
//初始化和更新props的方法
function updateProps(dom, oldProps, newProps) {
//初始化
if (newProps) {
//遍历新的属性对象
for (let key in newProps) {
if (key === 'children') {
continue
} else if (key === 'style') {
//如果是style的话就一个个追加进去
let styleObj = newProps[key]
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else {
//例如className就直接放上去即可
dom[key] = newProps[key]
}
}
}
//更新操作,如果有旧节点
if (oldProps) {
//旧的属性在新的属性中没有,则删除
for (let key in oldProps) {
if (!newProps[key]) {
dom[key] = null
}
}
}
}
//处理子节点
//接收两个参数,一个是子节点,另一个是挂载节点
function changeChildren(children, dom) {
//有一个儿子的情况 对象
if (typeof children == 'object'&& children.type ) {
render(children, dom) //递归调用
//有多个儿子的情况 数组
} else if (Array.isArray(children)) {
//循环处理
children.forEach(child =>
render(child, dom)
)
}
}
//将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
let {
type, props,content } = vnode
let Ndom; //新的dom节点
//1、判断type是什么类型的,是文本还是元素并生成对应的节点
if (type === REACT_TEXT) {
//如果是一个文本类型的
Ndom = document.createTextNode(content) //注意:我们在前面已经把所有的文件节点处理为一个对象类型的了
} else {
Ndom = document.createElement(type) //div
}
//2、处理属性 {
children style:{
color:red,fontsize:16px} className="title" }
if (props) {
//为了后续处理更新操作
updateProps(Ndom, {
}, props)
//3、处理子节点
let children = props.children
if (children) {
changeChildren(children, Ndom)
}
}
return Ndom
}
function render(vnode, container) {
//将虚拟dom转变成真实dom
let dom = createDOM(vnode)
//将真实dom挂载到container上
container.appendChild(dom)
}
const ReactDOM = {
render
}
export default ReactDOM;
Résumer
Depuis, nous avons essentiellement compris comment React implémente le processus de rendu des éléments dans la vue.