Maison > Article > interface Web > Comment écrire votre propre DOM virtuel ? Présentation de la méthode
Pour créer votre propre DOM virtuel, vous devez savoir deux choses. Vous n'avez même pas besoin de fouiller dans le code source de React ou de toute autre implémentation du DOM virtuel car ils sont si volumineux et complexes - mais en fait, la partie principale du DOM virtuel ne prend que moins de 50 lignes de code.
Il existe deux concepts :
Tout d'abord, nous devons stocker l'arborescence DOM en mémoire d'une manière ou d'une autre. Cela peut être fait en utilisant des objets JS normaux. Supposons que nous ayons un arbre comme celui-ci :
Ça a l'air simple, non ? Comment le représenter avec un objet JS ?
{ type: ‘ul’, props: { ‘class’: ‘list’ }, children: [ { type: ‘li’, props: {}, children: [‘item 1’] }, { type: ‘li’, props: {}, children: [‘item 2’] } ] }
Il y a deux choses à noter ici :
{ type: ‘…’, props: { … }, children: [ … ] }
Mais il y a beaucoup de contenu exprimé de cette façon, les arbres Dom sont assez difficiles. Écrivons ici une fonction auxiliaire pour faciliter la compréhension :
function h(type, props, …children) { return { type, props, children }; }
Utilisez cette méthode pour réorganiser le code initial :
h(‘ul’, { ‘class’: ‘list’ }, h(‘li’, {}, ‘item 1’), h(‘li’, {}, ‘item 2’), );
Cela semble beaucoup plus simple et vous pouvez aller plus loin. JSX est utilisé ici, comme suit :
est compilé en :
React.createElement(‘ul’, { className: ‘list’ }, React.createElement(‘li’, {}, ‘item 1’), React.createElement(‘li’, {}, ‘item 2’), );
Cela vous semble-t-il familier ? Si nous pouvons remplacer h(...)
par la fonction React.createElement(…)
que nous venons de définir, alors nous pouvons également utiliser la syntaxe JSX. En fait, il vous suffit d'ajouter ce commentaire en tête du fichier source :
/** @jsx h */
Il dit en fait à Babel 'Hé, petit frère, aide-moi à compiler la syntaxe JSX et à utiliser le h(...)
fonctionne à la place React.createElement(…)
, puis Babel commence à compiler. '
Pour résumer, on écrit le DOM comme ceci :
/** @jsx h */ const a = (
Babel va nous aider à le compiler en code comme ceci :
const a = ( h(‘ul’, { className: ‘list’ }, h(‘li’, {}, ‘item 1’), h(‘li’, {}, ‘item 2’), ); );
Quand la fonction “h”
est exécuté, il renverra un objet JS normal - c'est-à-dire notre DOM virtuel :
const a = ( { type: ‘ul’, props: { className: ‘list’ }, children: [ { type: ‘li’, props: {}, children: [‘item 1’] }, { type: ‘li’, props: {}, children: [‘item 2’] } ] } );
D'accord, nous avons maintenant l'arborescence DOM, représentée par un objet JS normal , Et notre propre structure. C'est cool, mais nous devons en créer un véritable DOM.
Faisons d'abord quelques hypothèses et déclarons quelques termes :
$
' pour représenter de vrais nœuds DOM (éléments, nœuds de texte), donc $ parent le fera être un véritable élément DOM node
* Tout comme dans React, il ne peut y avoir qu'un seul nœud racine - —Tous les autres nœuds sont à l'intérieur
Alors, écrivons une fonction createElement(…)
qui obtiendra un nœud DOM virtuel et renverra un nœud DOM réel. Ignorez les attributs props
et children
ici :
function createElement(node) { if (typeof node === ‘string’) { return document.createTextNode(node); } return document.createElement(node.type); }
Avec la méthode ci-dessus, je peux également créer deux types de nœuds, à savoir les nœuds de texte et les nœuds d'éléments Dom, qui sont des objets JS de type :
{ type: ‘…’, props: { … }, children: [ … ] }
Par conséquent, vous pouvez transmettre des nœuds de texte factices et des nœuds d'éléments factices dans functioncreateElement
- cela fonctionne.
Considérons maintenant les nœuds enfants – chacun d’eux est un nœud ou un élément de texte. Ils peuvent donc également être créés avec la fonction createElement(…). Oui, c'est comme la récursion, nous pouvons donc appeler createElement(…) pour les enfants de chaque élément, puis utiliser appendChild()
pour ajouter à notre élément :
function createElement(node) { if (typeof node === ‘string’) { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; }
Wow, ça a l'air bien. Mettons d’abord de côté l’attribut node props
. On se parle plus tard. Nous n'avons pas besoin qu'ils comprennent les concepts de base du DOM virtuel car ils ajoutent de la complexité.
Le code complet est le suivant :
/** @jsx h */ function h(type, props, ...children) { return { type, props, children }; } function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } const a = (
Nous pouvons maintenant convertir le DOM virtuel en un DOM réel, ce qui nécessite de comparer les deux arbres différences d'arbre DOM. Fondamentalement, nous avons besoin d'un algorithme pour comparer le nouvel arbre avec l'ancien arbre, ce qui nous permet de savoir ce qui a changé, puis de modifier le vrai DOM en conséquence.
Comment comparer les arbres DOM ? Les situations suivantes doivent être gérées :
Si les nœuds sont les mêmes - vous devez comparer profondément les nœuds enfants
编写一个名为 updateElement(…) 的函数,它接受三个参数—— $parent
、newNode 和 oldNode,其中 $parent 是虚拟节点的一个实际 DOM 元素的父元素。现在来看看如何处理上面描述的所有情况。
function updateElement($parent, newNode, oldNode) { if (!oldNode) { $parent.appendChild( createElement(newNode) ); } }
这里遇到了一个问题——如果在新虚拟树的当前位置没有节点——我们应该从实际的 DOM 中删除它—— 这要如何做呢?
如果我们已知父元素(通过参数传递),我们就能调用 $parent.removeChild(…)
方法把变化映射到真实的 DOM 上。但前提是我们得知道我们的节点在父元素上的索引,我们才能通过 $parent.childNodes[index] 得到该节点的引用。
好的,让我们假设这个索引将被传递给 updateElement 函数(它确实会被传递——稍后将看到)。代码如下:
function updateElement($parent, newNode, oldNode, index = 0) { if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } }
首先,需要编写一个函数来比较两个节点(旧节点和新节点),并告诉节点是否真的发生了变化。还有需要考虑这个节点可以是元素或是文本节点:
function changed(node1, node2) { return typeof node1 !== typeof node2 || typeof node1 === ‘string’ && node1 !== node2 || node1.type !== node2.type }
现在,当前的节点有了 index 属性,就可以很简单的用新节点替换它:
function updateElement($parent, newNode, oldNode, index = 0) { if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } else if (changed(newNode, oldNode)) { $parent.replaceChild( createElement(newNode), $parent.childNodes[index] ); } }
最后,但并非最不重要的是——我们应该遍历这两个节点的每一个子节点并比较它们——实际上为每个节点调用updateElement(…)方法,同样需要用到递归。
undefined
也没有关系,我们的函数也会正确处理它。function updateElement($parent, newNode, oldNode, index = 0) { if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } else if (changed(newNode, oldNode)) { $parent.replaceChild( createElement(newNode), $parent.childNodes[index] ); } else if (newNode.type) { const newLength = newNode.children.length; const oldLength = oldNode.children.length; for (let i = 0; i <h2>完整的代码</h2><p><strong>Babel+JSX</strong><br>/<em>* @jsx h </em>/</p><pre class="brush:php;toolbar:false">function h(type, props, ...children) { return { type, props, children }; } function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } function changed(node1, node2) { return typeof node1 !== typeof node2 || typeof node1 === 'string' && node1 !== node2 || node1.type !== node2.type } function updateElement($parent, newNode, oldNode, index = 0) { if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } else if (changed(newNode, oldNode)) { $parent.replaceChild( createElement(newNode), $parent.childNodes[index] ); } else if (newNode.type) { const newLength = newNode.children.length; const oldLength = oldNode.children.length; for (let i = 0; i
HTML
<button>RELOAD</button> <p></p>
CSS
#root { border: 1px solid black; padding: 10px; margin: 30px 0 0 0; }
打开开发者工具,并观察当按下“Reload”按钮时应用的更改。
现在我们已经编写了虚拟 DOM 实现及了解它的工作原理。作者希望,在阅读了本文之后,对理解虚拟 DOM 如何工作的基本概念以及在幕后如何进行响应有一定的了解。
然而,这里有一些东西没有突出显示(将在以后的文章中介绍它们):
原文地址:https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060
作者:deathmood
为了保证的可读性,本文采用意译而非直译。
更多编程相关知识,请访问:编程入门!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!