真实DOM
浏览器渲染引擎工作流程,大致分为五步,创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting
第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。
第二步,用CSS分析器,分析CSS文件和元素上的inline样式,生成页面的样式表。
第三步,将DOM树和样式表,关联起来,构建一颗Render树(这一过程又称为Attachment)。每个DOM节点都有attach方法,接受样式信息,返回一个render对象(又名renderer)。这些render对象最终会被构建成一颗Render树。
第四步,有了Render树,浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标。
第五步,Render树和节点显示坐标都有了,就调用每个节点paint方法,把它们绘制出来。
虚拟DOM
- state数据
- JSX模板
- 数据+模板相结合,生成虚拟DOM 比如
['div', {id: 'abc'}, ['span', {}, 'hello']]
-
用虚拟DOM的结构生成真实的DOM
<div id = 'abc'><span>hello</span></div>
-
state发生变化
-
数据+模板生成新的虚拟DOM
['div', {id: 'abc'}, ['span', {}, 'bye']]
-
比较原始虚拟DOM新的虚拟DOM的区别,找到区别是span中的内容
-
直接操作DOM,改变span中得内容
好处:
Web界面由DOM树(树的意思是数据结构)来构建,当其中一部分发生变化时,其实就是对应某个DOM节点发生了变化,虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制
react在哪里创建虚拟dom?
每次react中的state或者props改变的时候会触发组件中的render函数,父组件触发render函数时子组件也会跟着触发render函数,而虚拟DOM即是在render函数中被创建
render() {
return <div id='abc'><span>hello</span></div>
}
上述代码中的return的内容是JSX模板,接收三个参数,一创建的标签、二属性、三内容。
render() {
return React.createElement('div', {id: 'abc'}, React.createElement('span', {}, 'hello'))
}
两个代码等价。
虚拟DOM中的diff算法
用虚拟DOM完成数据驱动涉及到关键的一点就是我们如何比较两个虚拟DOM的差异。
首先我们得确定发生差异的来由,归根结底是组件的state发生了变化,调用了setState方法,之后我们就会生成新的虚拟DOM与旧的进行比对
1.同级比较
diff算法中只会比较同层级的元素,一旦发现某一级之间有所不同,则会弃置其子级,直接用从新的差异的一级以及其下的所有子级替换老的。
2.引用key值
for循环中如果没有给每个item所在标签增加一个key值,vue和react中都会发出警告,建议加上,这是因为当进行虚拟DOM比对时,需要比较出相同的元素和不同的,没有key就很难一一对应,需要做两层循环比较,用上了key值则可以清楚比较出哪一个新增或删除了什么。
使用refs
下面是几个合适使用refs的情况
- 管理焦点,文本选择或者媒体播放
- 触发强制动画
- 集成第三方DOM库
创建Refs
Refs 是使用 React.createRef()
创建的,并通过 ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
访问Refs
当 ref 被传递给 render
中的元素时,对该节点的引用可以在 ref 的 current
属性中被访问。
ref的值根据节点的类型而有所不同
- 当
ref
属性用于 HTML 元素时,构造函数中使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性。 - 当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性。