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
- alert('hi!')}>item 1
- {/* this node will always be updated */}
- text
到这里我们就完成了一个最简单的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

HTMLISNOTAPROGRAMMENGUAGE; ITISAMARKUMARKUPLAGUAGE.1)htmlStructures andFormatSwebContentusingtags.2)itworkswithcsssforstylingandjavascript for Interactivity,增强WebevebDevelopment。

HTML是构建网页结构的基石。1.HTML定义内容结构和语义,使用、、等标签。2.提供语义化标记,如、、等,提升SEO效果。3.通过标签实现用户交互,需注意表单验证。4.使用、等高级元素结合JavaScript实现动态效果。5.常见错误包括标签未闭合和属性值未加引号,需使用验证工具。6.优化策略包括减少HTTP请求、压缩HTML、使用语义化标签等。

HTML是一种用于构建网页的语言,通过标签和属性定义网页结构和内容。1)HTML通过标签组织文档结构,如、。2)浏览器解析HTML构建DOM并渲染网页。3)HTML5的新特性如、、增强了多媒体功能。4)常见错误包括标签未闭合和属性值未加引号。5)优化建议包括使用语义化标签和减少文件大小。

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

HTML的作用是通过标签和属性定义网页的结构和内容。1.HTML通过到、等标签组织内容,使其易于阅读和理解。2.使用语义化标签如、等增强可访问性和SEO。3.优化HTML代码可以提高网页加载速度和用户体验。

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

HTML、CSS和JavaScript是Web开发的三大支柱。1.HTML定义网页结构,使用标签如、等。2.CSS控制网页样式,使用选择器和属性如color、font-size等。3.JavaScript实现动态效果和交互,通过事件监听和DOM操作。

HTML定义网页结构,CSS负责样式和布局,JavaScript赋予动态交互。三者在网页开发中各司其职,共同构建丰富多彩的网站。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版

Dreamweaver CS6
视觉化网页开发工具

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中