搜尋
首頁web前端html教學从零开始一步一步写一个简单的 Virtual DOM 实现 2 :Props&Event 处理_html/css_WEB-ITnose

Props

首先我们要回顾下前文讲的一个有些偏差的小点,假设我们在JSX中只写一个最简单的Div:

<div></div>

Babel会自动将该JSX转化为如下的DOM表达式:

{ type: ‘’, props: null, children: [] }

注意,这里的props默认是null,我们在之前的文章中并没有关注到这个属性,而本部分则是要讲解Virtual DOM中Props的用法。一般来说,无论在哪种编程环境下都要尽量避免Null的出现,因此我们首先来改造下 h 函数,使得其能够默认返回一个空的Object,而不是Null:

function h(type, props, …children) { return { type, props: props || {}, children };}

Setting Props:设置Props

接触过React的同学对于Props肯定不会陌生,而设置Props也就跟使用普通的HTML标签属性很类似:

<ul className=”list” style=”list-style: none;”></ul>

而最终会转化为如下的表达式:

{  type: ‘ul’,  props: { className: ‘list’, style: ’list-style: none;’ }  children: []}

props对象中的每个键即为属性名,而值为属性值,一般来说我们只需要简单的调用一个 setAttribute 方法来讲这个Props中的键值对设置到DOM元素上即可:

function setProp($target, name, value) { $target.setAttribute(name, value);}

这个函数用于将单个的Prop值设置到DOM元素上,而对于props对象,我们要做的就是依次遍历:

function setProps($target, props) { Object.keys(props).forEach(name => {   setProp($target, name, props[name]); });}

你应该还记得那个用于创建元素的 createElement 方法吧,我们需要将 setProps 方法放置到元素成功创建之后:

function createElement(node) { if (typeof node === ‘string’) {   return document.createTextNode(node); } const $el = document.createElement(node.type); setProps($el, node.props); node.children   .map(createElement)   .forEach($el.appendChild.bind($el)); return $el;}

不要急,这还远远不够。React的初学教程中一直强调className与class的区别,在我们的setProps中也需要对于这些JS的保留字做一个替换,譬如:

<nav className=”navbar light”> <ul></ul></nav>

另外,还有比较常见的就是对于DOM的布尔属性,譬如checked、disabled等等的处理:

<input type=”checkbox” checked={false} />

在真实的DOM节点上,如果是出现了false的情况,我们并不希望checked属性会出现,那么我们的Props函数就要能智能地进行判断:

function setBooleanProp($target, name, value) { if (value) {   $target.setAttribute(name, value);   $target[name] = true; } else {   $target[name] = false; }}

最后呢,要做的就是对于自定义的,即非标准的HTML属性进行一个过滤,这些属性只应该出现在JS对象上,而不应该出现在真实的DOM对象上:

function isCustomProp(name) { return false;}
function setProp($target, name, value) { if (isCustomProp(name)) {   return; } else if (name === ‘className’) {   $target.setAttribute(‘class’, value); } else if (typeof value === ‘boolean’) {   setBooleanProp($target, name, value); } else {   $target.setAttribute(name, value); }}

总结一下,本部分完整的JSX代码为:

/** @jsx h */function h(type, props, ...children) {  return { type, props: props || {}, children };}function setBooleanProp($target, name, value) {  if (value) {    $target.setAttribute(name, value);    $target[name] = true;  } else {    $target[name] = false;  }}function isCustomProp(name) {  return false;}function setProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.setAttribute('class', value);  } else if (typeof value === 'boolean') {    setBooleanProp($target, name, value);  } else {    $target.setAttribute(name, value);  }}function setProps($target, props) {  Object.keys(props).forEach(name => {    setProp($target, name, props[name]);  });}function createElement(node) {  if (typeof node === 'string') {    return document.createTextNode(node);  }  const $el = document.createElement(node.type);  setProps($el, node.props);  node.children    .map(createElement)    .forEach($el.appendChild.bind($el));  return $el;}//--------------------------------------------------const f = (  <ul style="list-style: none;">    <li className="item">item 1</li>    <li className="item">      <input type="checkbox" checked={true} />      <input type="text" disabled={false} />    </li>  </ul>);const $root = document.getElementById('root');$root.appendChild(createElement(f));

Diffing Props:Props变化比较

现在我们已经创建了带有Props属性的元素,下一个需要考虑的就是应该如何应用到我们上文提到的Diff算法中。首先我们要来看下如何从真实的DOM中移除某些Props:

function removeBooleanProp($target, name) { $target.removeAttribute(name); $target[name] = false;}function removeProp($target, name, value) { if (isCustomProp(name)) {   return; } else if (name === ‘className’) {   $target.removeAttribute(‘class’); } else if (typeof value === ‘boolean’) {   removeBooleanProp($target, name); } else {   $target.removeAttribute(name); }}

然后我们需要写一个updateProp函数,来根据新旧节点的Props的变化进行恰当的真实DOM节点的修改,共有以下几种情况:

  • 新节点移除了某个旧节点的Prop

  • 新节点添加了某个旧节点没有的Prop

  • 新旧节点的某个Prop的值发生了变化

根据以上规则,我们可知更新Prop的函数为:

function updateProp($target, name, newVal, oldVal) { if (!newVal) {   removeProp($target, name, oldVal); } else if (!oldVal || newVal !== oldVal) {   setProp($target, name, newVal); }}

可以看出,更新单个Prop的函数还是非常简单的,就是将移除与设置结合起来使用,那么我们扩展到Props,就得到如下的函数:

function updateProps($target, newProps, oldProps = {}) {  const props = Object.assign({}, newProps, oldProps);  Object.keys(props).forEach(name => {    updateProp($target, name, newProps[name], oldProps[name]);  });}

同样地,我们需要将该更新函数添加到 updateElement 函数中:

function updateElement($parent, newNode, oldNode, index = 0) {  ...  } else if (newNode.type) {    updateProps(      $parent.childNodes[index],      newNode.props,      oldNode.props    );     ...  }}

Events

用户交互是任何一个应用不可或缺的部分,而在这里我们讨论下如何为Virtual DOM添加事件处理的能力,React大概会这么做:

<button onClick={() => alert(‘hi!’)}></button>

可以看出,设置一个事件处理器就是添加一个Prop,只不过名称会以 on 开始,那么我们可以用如下函数来判断某个Prop是否与事件相关:

function isEventProp(name) { return /^on/.test(name);}

判断是事件类型之后,我们可以提取出事件名:

function extractEventName(name) { return name.slice(2).toLowerCase();}

看到这里,估计你会考虑直接将事件处理也放到setProps与updateProps函数中,不过这边就会存在一个问题,在diffProps的时候,你很难去比较两个function:

因此我们将所有的事件类型的Props认为是自定义的Props,这样我们上面提到的isCustomProp就起作用了:

function isCustomProp(name) { return isEventProp(name);}

而把事件响应函数绑定到真实的DOM节点也很简单:

function addEventListeners($target, props) { Object.keys(props).forEach(name => {   if (isEventProp(name)) {     $target.addEventListener(       extractEventName(name),       props[name]     );   } });}

同样的需要将该函数添加到createElement中:

function createElement(node) { if (typeof node === ‘string’) {   return document.createTextNode(node); } const $el = document.createElement(node.type); setProps($el, node.props); addEventListeners($el, node.props); node.children   .map(createElement)   .forEach($el.appendChild.bind($el)); return $el;}

Re-Adding Events:重新设置了事件响应

在这里我们暂时不考虑地很复杂,即不深入地比较那些事件类型的Prop发生变化的情况,作为替代的,我们引入一个forceUpdate属性,即强制整个DOM进行更新:

function changed(node1, node2) { return typeof node1 !== typeof node2 ||        typeof node1 === ‘string’ && node1 !== node2 ||        node1.type !== node2.type ||        node.props.forceUpdate;}
function isCustomProp(name) { return isEventProp(name) || name === ‘forceUpdate’;}

最后,本文完整的JSX为:

/** @jsx h */function h(type, props, ...children) {  return { type, props: props || {}, children };}function setBooleanProp($target, name, value) {  if (value) {    $target.setAttribute(name, value);    $target[name] = true;  } else {    $target[name] = false;  }}function removeBooleanProp($target, name) {  $target.removeAttribute(name);  $target[name] = false;}function isEventProp(name) {  return /^on/.test(name);}function extractEventName(name) {  return name.slice(2).toLowerCase();}function isCustomProp(name) {  return isEventProp(name) || name === 'forceUpdate';}function setProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.setAttribute('class', value);  } else if (typeof value === 'boolean') {    setBooleanProp($target, name, value);  } else {    $target.setAttribute(name, value);  }}function removeProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.removeAttribute('class');  } else if (typeof value === 'boolean') {    removeBooleanProp($target, name);  } else {    $target.removeAttribute(name);  }}function setProps($target, props) {  Object.keys(props).forEach(name => {    setProp($target, name, props[name]);  });}function updateProp($target, name, newVal, oldVal) {  if (!newVal) {    removeProp($target, name, oldVal);  } else if (!oldVal || newVal !== oldVal) {    setProp($target, name, newVal);  }}function updateProps($target, newProps, oldProps = {}) {  const props = Object.assign({}, newProps, oldProps);  Object.keys(props).forEach(name => {    updateProp($target, name, newProps[name], oldProps[name]);  });}function addEventListeners($target, props) {  Object.keys(props).forEach(name => {    if (isEventProp(name)) {      $target.addEventListener(        extractEventName(name),        props[name]      );    }  });}function createElement(node) {  if (typeof node === 'string') {    return document.createTextNode(node);  }  const $el = document.createElement(node.type);  setProps($el, node.props);  addEventListeners($el, node.props);  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 ||         node1.props && node1.props.forceUpdate;}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) {    updateProps(      $parent.childNodes[index],      newNode.props,      oldNode.props    );    const newLength = newNode.children.length;    const oldLength = oldNode.children.length;    for (let i = 0; i < newLength || i < oldLength; i++) {      updateElement(        $parent.childNodes[index],        newNode.children[i],        oldNode.children[i],        i      );    }  }}//---------------------------------------------------------function log(e) {  console.log(e.target.value);}const f = (  
  • alert('hi!')}>item 1
  • {/* this node will always be updated */}
  • text
);const g = (
  • alert('hi!')}>item 1
  • {/* this node will always be updated */}
  • text
);const $root = document.getElementById('root');const $reload = document.getElementById('reload');updateElement($root, f);$reload.addEventListener('click', () => { updateElement($root, g, f);});

到这里我们就完成了一个最简单的Virtual DOM算法,不过其与真正能够投入实战的Virtual DOM算法还是有很大距离,进一步阅读推荐:

  • 深度剖析:如何实现一个 Virtual DOM 算法

  • 我的前端故事----React算法又是个什么鬼?!

  • A Virtual DOM and diffing algorithm :一个比较复杂的Virtual DOM算法的实现

  • simple-virtual-dom :一个简单的Virtual DOM的实现

  • how-to-write-your-own-virtual-dom

  • Virtual DOM Benchmark

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
了解HTML,CSS和JavaScript:初學者指南了解HTML,CSS和JavaScript:初學者指南Apr 12, 2025 am 12:02 AM

WebDevelovermentReliesonHtml,CSS和JavaScript:1)HTMLStructuresContent,2)CSSStyleSIT和3)JavaScriptAddSstractivity,形成thebasisofmodernWebemodernWebExexperiences。

HTML的角色:構建Web內容HTML的角色:構建Web內容Apr 11, 2025 am 12:12 AM

HTML的作用是通過標籤和屬性定義網頁的結構和內容。 1.HTML通過到、等標籤組織內容,使其易於閱讀和理解。 2.使用語義化標籤如、等增強可訪問性和SEO。 3.優化HTML代碼可以提高網頁加載速度和用戶體驗。

HTML和代碼:仔細觀察術語HTML和代碼:仔細觀察術語Apr 10, 2025 am 09:28 AM

htmlisaspecifictypefodyfocusedonstructuringwebcontent,而“代碼” badlyLyCludEslanguagesLikeLikejavascriptandPytyPythonForFunctionality.1)htmldefineswebpagertuctureduseTags.2)“代碼”代碼“ code” code code code codeSpassSesseseseseseseseAwiderRangeLangeLangeforLageforLogageforLogicIctInterract

HTML,CSS和JavaScript:Web開發人員的基本工具HTML,CSS和JavaScript:Web開發人員的基本工具Apr 09, 2025 am 12:12 AM

HTML、CSS和JavaScript是Web開發的三大支柱。 1.HTML定義網頁結構,使用標籤如、等。 2.CSS控製網頁樣式,使用選擇器和屬性如color、font-size等。 3.JavaScript實現動態效果和交互,通過事件監聽和DOM操作。

HTML,CSS和JavaScript的角色:核心職責HTML,CSS和JavaScript的角色:核心職責Apr 08, 2025 pm 07:05 PM

HTML定義網頁結構,CSS負責樣式和佈局,JavaScript賦予動態交互。三者在網頁開發中各司其職,共同構建豐富多彩的網站。

HTML容易為初學者學習嗎?HTML容易為初學者學習嗎?Apr 07, 2025 am 12:11 AM

HTML適合初學者學習,因為它簡單易學且能快速看到成果。 1)HTML的學習曲線平緩,易於上手。 2)只需掌握基本標籤即可開始創建網頁。 3)靈活性高,可與CSS和JavaScript結合使用。 4)豐富的學習資源和現代工具支持學習過程。

HTML中起始標籤的示例是什麼?HTML中起始標籤的示例是什麼?Apr 06, 2025 am 12:04 AM

AnexampleOfAstartingTaginHtmlis,beginSaparagraph.startingTagSareEssentialInhtmlastheyInitiateEllements,defiteTheeTheErtypes,andarecrucialforsstructuringwebpages wepages webpages andConstructingthedom。

如何利用CSS的Flexbox佈局實現菜單中虛線分割效果的居中對齊?如何利用CSS的Flexbox佈局實現菜單中虛線分割效果的居中對齊?Apr 05, 2025 pm 01:24 PM

如何設計菜單中的虛線分割效果?在設計菜單時,菜名和價格的左右對齊通常不難實現,但中間的虛線或點如何...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。