您现在的位置是:网站首页> 编程资料编程资料
解析React 中的Virtual DOM_MsSql_
2023-05-26
339人已围观
简介 解析React 中的Virtual DOM_MsSql_
React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解State和Props,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。
React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解State和Props,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。
这是Choerodon的一个前端页面

在复杂的前端项目中一个页面可能包含上百个状态,对React框架理解得更精细一些对前端优化很重要。曾经这个页面点击一条记录展示详情会卡顿数秒,而这仅仅是前端渲染造成的。
为了能够解决这些问题,开发者需要了解React组件从定义到在页面上呈现(然后更新)的整个过程。
React在编写组件时使用混合HTML和JavaScript的一种语法(称为JSX)。 但是,浏览器对JSX及其语法一无所知,浏览器只能理解纯JavaScript,因此必须将JSX转换为HTML。 这是一个div的JSX代码,它有一个类和一些内容:
文本
在React中将这段jsx变成普通的js之后它就是一个带有许多参数的函数调用:
React.createElement( 'div', { className: 'cn' }, '文本' ); React.createElement( 'div', { className: 'cn' }, ['Content 1!', React.createElement('br'), 'Content 2!'] ) 它的第一个参数是一个字符串,对应html中的标签名,第二个参数是它的所有属性所构成的对象,当然,它也有可能是个空对象,剩下的参数都是这个元素下的子元素,这里的文本也会被当作一个子元素,所以第三个参数是 `“文本”` 。 到这里你应该就能想象这个元素下有更多`children`的时候会发生什么。 ```html 文本1
文本2 React.createElement( 'div', { className: 'cn' }, '文本1', // 1st child React.createElement('br'), // 2nd child '文本1' // 3rd child )目前的函数有五个参数:元素的类型,全部属性的对象和三个子元素。 由于一个child也是React已知的HTML标签,因此它也将被解释成函数调用。
到目前为止,本文已经介绍了两种类型的child参数,一种是string纯文本,一种是调用其他的React.createElement函数。其实,其他值也可以作为参数,比如: - 基本类型 false,null,undefined和 true - 数组 - React组件
使用数组是因为可以将子组件分组并作为一个参数传递:
当然,React的强大功能不是来自`HTML`规范中描述的标签,而是来自用户创建的组件,例如:
```js function Table({ rows }) { return ( | {row.title} |
组件允许开发者将模板分解为可重用的块。在上面的“纯函数”组件的示例中,组件接受一个包含表行数据的对象数组,并返回React.createElement对table元素及其行作为子元素的单个调用 。
每当开发者将组件放入JSX布局中时它看上去是这样的:
但从浏览器角度,它看到的是这样的:
React.createElement(Table, { rows: rows });请注意,这次的第一个参数不是以string描述的HTML元素,而是组件的引用(即函数名)。第二个参数是传入该组件的props对象。
将组件放在页面上
现在,浏览器已经将所有JSX组件转换为纯JavaScript,现在浏览器获得了一堆函数调用,其参数是其他函数调用,还有其他函数调用……如何将它们转换为构成网页的DOM元素?
为此,开发者需要使用ReactDOM库及其render方法:
function Table({ rows }) { /* ... */ } // 组件定义 // 渲染一个组件 ReactDOM.render( React.createElement(Table, { rows: rows }), // "创建" 一个 component document.getElementById('#root') // 将它放入DOM中 );当ReactDOM.render被调用时,React.createElement最终也会被调用,它返回以下对象:
// 这个对象里还有很多其他的字段,但现在对开发者来说重要的是这些。 { type: Table, props: { rows: rows }, // ... }这些对象构成了React意义上的Virtual DOM
它们将在所有进一步渲染中相互比较,并最终转换为真正的DOM(与Virtual DOM对比)。
这是另一个例子:这次有一个div具有class属性和几个子节点:
React.createElement( 'div', { className: 'cn' }, 'Content 1!', 'Content 2!', );变成:
{ type: 'div', props: { className: 'cn', children: [ 'Content 1!', 'Content 2!' ] } }所有的传入的展开函数,也就是React.createElement除了第一第二个参数剩下的参数都会在props对象中的children属性中,不管传入的是什么函数,他们最终都会作为children传入props中。
而且,开发者可以直接在JSX代码中添加children属性,将子项直接放在children中,结果仍然是相同的:
在Virtual DOM对象被建立出来之后ReactDOM.render会尝试按以下规则把它翻译成浏览器能够看得懂的DOM节点: -如果Virtual DOM对象中的type属性是一个string类型的tag名称,就创建一个tag,包含props里的全部属性。 -如果Virtual DOM对象中的type属性是一个函数或者class,就调用它,它返回的可能还是一个Virtual DOM然后将结果继续递归调用此过程。 -如果props中有children属性,就对children中的每个元素进行以上过程,并将返回的结果放到父DOM节点中。
最后,浏览器获得了以下HTML(对于上述table的例子):
| Title |
重建DOM
接下浏览器要“重建”一个DOM节点,如果浏览器要更新一个页面,显然,开发者并不希望替换页面中的全部元素,这就是React真正的魔法了。如何才能实现它?先从最简单的方法开始,重新调用这个节点的ReactDOM.render方法。
// 第二次调用 ReactDOM.render( React.createElement(Table, { rows: rows }), document.getElementById('#root') );这一次,上面的代码执行逻辑将与看到的代码不同。React不是从头开始创建所有DOM节点并将它们放在页面上,React将使用“diff”算法,以确定节点树的哪些部分必须更新,哪些部分可以保持不变。
那么它是怎样工作的?只有少数几个简单的情况,理解它们将对React程序的优化有很大帮助。请记住,接下来看到的对象是用作表示React Virtual DOM中节点的对象。
▌Case 1:type是一个字符串,type在调用之间保持不变,props也没有改变。
// before update { type: 'div', props: { className: 'cn' } } // after update { type: 'div', props: { className: 'cn' } }这是最简单的情况:DOM保持不变。
▌Case 2:type仍然是相同的字符串,props是不同的。
// before update: { type: 'div', props: { className: 'cn' } } // after update: { type: 'div', props: { className: 'cnn' } }由于type仍然代表一个HTML元素,React知道如何通过标准的DOM API调用更改其属性,而无需从DOM树中删除节点。
▌Case 3:type已更改为不同的组件String或从String组件更改为组件。
// before update: { type: 'div', props: { className: 'cn' } } // after update: { type: 'span', props: { className: 'cn' } }由于React现在看到类型不同,它甚至不会尝试更新DOM节点:旧元素将与其所有子节点一起被删除(unmount)。因此,在DOM树上替换完全不同的元素的代价会非常之高。幸运的是,这在实际情况中很少发生。
重要的是要记住React使用===(三等)来比较type值,因此它们必须是同一个类或相同函数的相同实例。
下一个场景更有趣,因为这是开发者最常使用React的方式。
▌Case 4:type是一个组件。
// before update: { type: Table, props: { rows: rows } } // after update: { type: Table, props: { rows: rows } }你可能会说,“这好像没有任何变化”,但这是不对的。
如果type是对函数或类的引用(即常规React组件),并且启动了树diff比较过程,那么React将始终尝试查看组件内部的所有child以确保render的返回值没有更改。即在树下比较每个组件 - 是的,复杂的渲染也可能变得昂贵!
组件中的children
除了上面描述的四种常见场景之外,当元素有多个子元素时,开发者还需要考虑React的行为。假设有这样一个元素:
// ... props: { children: [ { type: 'div' }, { type: 'span' }, { type: 'br' } ] }, // ...开发者开发者想将它重新渲染成这样(span和div交换了位置):
// ... props: { children: [ { type: 'span' }, { type: 'div' }, { type: 'br' } ] }, // ...那么会发生什么?
当React看到里面的任何数组类型的props.children,它会开始将它中的元素与之前看到的数组中的元素按顺序进行比较:index 0将与index 0,index 1与index 1进行比较,对于每对子元素,React将应用上述规则集进行比较更新。在以上的例子中,它看到div变成一个span这是一个情景3中的情况。但这有一个问题:假设开发者想要从1000行表中删除第一行。React必须“更新”剩余的999个孩子,因为如果与先前的逐个索引表示相比,他们的内容现在将不相等。
幸运的是,React有一种内置的方法来解决这个问题。如果元素具有key属性,则元素将通过key而不是索引进行比较。只要key是唯一的,React就会移动元素而不将它们从DOM树中移除,然后将它们放回(React中称为挂载/卸载的过程)。
// ... props: { children: [ // 现在react就是根据key,而不是索引来比较了 { type: 'div', key: 'div' }, { type: 'span', key: 'span' }, { type: 'br', key: 'bt' } ] }, // ...当状态改变时
到目前为止,本文只触及了props,React哲学的一部分,但忽略了state。这是一个简单的“有状态”组件:
class App extends Component { state = { counter: 0 } increment = () => this.setState({ counter: this.state.counter + 1, }) render = () => () }现在,上述例子中的state对象有一个counter属性。单击按钮会增加其值并更改按钮文本。但是当用户点击时,DOM会发生什么?它的哪一部分将被重新计算和更新?
调用this.setState也会导致重新渲染,但不会导致整个页面重渲染,而只会导致组件本身及其子项。父母和兄弟姐妹都可以幸免于难。
修复问题
本文准备了一个DEMO,这是修复问题前的样子。你可以在这里查看其源代码。不过在此之前,你还需要安装React Developer Tools。
打开demo要看的第一件事是哪些元素以及何时导致Virtual DOM更新。导航到浏览器的Dev Tools中的React面板,点击设置然后选择“Highlight Updates”复选框:

现在尝试在表中添加一行。如你所见,页面上的每个元素周围都会出现边框。这意味着每次添加行时,React都会计算并比较整个Virtual DOM树。现在尝试按一行内的计数器按钮。你将看到Virtual DOM如何更新 (state仅相关元素及其子元素更新)。
React DevTools暗示了问题可能出现的地方,但没有告诉开发者任何细节:特别是有问题的更新是指元素“diff”之后有不同,还是组件被unmount/mount了。要了解更多信息,开发者需要使用React的内置分析器(请注意,它不能在生产模式下工作)。
转到Chrome DevTools中的“Performance”标签。点击record按钮,然后点击表格。添加一些行,更改一些计数器,然后点击“Stop”按钮。稍等一会儿之后开发者会看到:

在结果输出中,开发者需要关注“Timing”。缩放时间轴,直到看到“React Tree Reconciliation”组及其子项。这些都是组件的名称,旁边有[update]或[mount]。可以看到有一个TableRow被mount了,其他所有的TableRow都在update,这并不是开发者想要的。
大多数性能问题都由[update]或[mount]引起
一个组件(以及组件下的所有东西)由于某种原因在每次更新时重新挂载,开发者不想让它发生(重新挂载很慢),或者在大型分支上执行代价过大的重绘,即使组件似乎没有发生任何改变。
修复mount/unmount
现在,当开发者了解React如何决定更新Virtual DOM并知道幕后发生的事情时,终于准备好解决问题了!修复性能问题首先要解决 mount/unmount。
如果开发者将任何元素/组件的多个子元素在内部表示为数组,那么程序可以获得非常明显的速度提升。
考虑一下:
在虚拟DOM中,将表示为:
// ... props: { children: [ { type: Message }, { type: Table }, { type: Footer } ] } // ...一个简单的Message组件(是一个div带有一些文本,像是猪齿鱼的顶部通知)和一个很长的Table,比方说1000多行。它们都是div元素的child,因此它们被放置在父节点的props.children之下,并且它们没有key提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- SQL server中提示对象名无效的解决方法_MsSql_
- SQL Server 使用 Pivot 和 UnPivot 实现行列转换的问题小结_MsSql_
- SQL Server Transact-SQL编程详解_MsSql_
- SQL Server 数据库基础编程详解_MsSql_
- SQL Server 数据库的设计详解_MsSql_
- SQL Server 的T-SQL高级查询详解_MsSql_
- SQL Server 索引和视图详解_MsSql_
- SQLServer2014故障转移群集的部署的图文教程_MsSql_
- SQL Server 事务,异常和游标详解_MsSql_
- SQL Server的存储过程详解_MsSql_
