虚拟DOM的原理
是什么:虚拟DOM就是虚拟节点。React用JS对象来模拟DOM节点,然后将其渲染成真实的DOM节点。
怎么做
第一步是模拟,用JSX语法写出来的div其实是一个虚拟节点:
<div id="x">
<span class="red">hi</span>
</div>
这个代码会得到这样的一个对象:
{
tag:'div',
props:{
id:'x'
},
children:[
{
tag:'span',
props:{
className:'red'
},
children: [
'hi'
]
}
]
}
能做到这一点是因为JSX语法会被转译为createElement
函数调用,如下:
React.createElement("div", {id: "x"},
React.createElement('span', {class: "red"}, "hi")
)
第二步是将虚拟节点渲染成真实节点
function render(vdom){
//如果是字符串或者数字,创建一个文本节点
if(typeof vdom === 'string' || typeof vdom === 'number'){
return document.createTextNode(vdom)
}
const {tag,props,children} = vdom
//创建真实DOM
const element = document.createElement(tag)
//设置属性
setProps(element,props)
//遍历子节点,并获取创建真实DOM,插入当前节点
children
.map(render)
.forEach(element.appendChild(element))
//虚拟DOM中缓存真实DOM节点
vdom.dom = element
//返回DOM节点
return element
}
function setProps(){}//略
//作者:Shenfq
注意:如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过diff算法得到一个patch再更新到真实节点上。
解决了什么问题
- DOM操作性能问题。通过虚拟DOM和diff算法减少不必要的DOM操作,保证性能不太差。
- DOM操作不方便问题。以前各种DOM API要记,现在只有
setState
优点
- 为React带来了跨平台能力,因为虚拟节点除了渲染为真实节点还可以渲染为其他东西。
- 让DOM操作的整体性能更好,通过diff溅洒红不必要的DOM操作。
缺点
-
性能要求极高的地方,还是需要用真实DOM操作
-
React为虚拟DOM创造了合成时间,和原生DOM事件不太一样
a. 所有React事件都绑定的到根元素,自动实现事件委托
b. 如果混用合成事件和原生DOM事件,有可能出现bug
DOM diff 算法是怎么样的
是什么
DOM diff 就是对比两棵虚拟DOM树的算法。当组件变化时,会render出一个新的虚拟DOM,diff算法对比新旧虚拟DOM之后,得到一个patch,然后React用patch来更新真实DOM。
怎么做
-
首先对比两棵树的根节点
a. 如果根节点的类型改变了,比如div变成了p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实DOM树,创建真实DOM树。
b. 如果根节点的类型没变,就看属性有没有变化
i.如果没变,就保留对应真实节点
ii. 如果变了,就只更新该节点的属性,不重新创建节点。更新style时,如果多个css属性只有一个改变了,那么React只更新改变的。
-
然后同时遍历两棵树的子节点,每个节点的对比过程同上
特殊情况:
<ul>
<li>B</li>
<li>C</li>
</ul>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
React会对比B-A,会删除B文本新建A文本;对比C-B,会产出C文本,新建B文本;(把操作会汇总patch里再进行DOM操作)对比空-C,会新建C文本。但是我们可以发现主需要创建A文本,保留B和C即可。
因为React需要你加key
<ul>
<li key="b">B</li>
<li key="c">C</li>
</ul>
<ul>
<li key="a">A</li>
<li key="b">B</li>
<li key="c">C</li>
</ul>
这样React线对比key发现key只新增了一个,于是保留b和c,新建a。 以上是React的diff算法。
Vue 双端交叉对比
假设有旧的Vnode数组和新的Vnode数组这两个数组,而且有四个变量充当指针分别指到两个数组的头尾.
重复下面的对比过程,直到两个数组中任一数组的头指针超过尾指针,循环结束 :
- 头头对比: 对比两个数组的头部,如果找到,把新节点patch到旧节点,头指针后移
- 尾尾对比: 对比两个数组的尾部,如果找到,把新节点patch到旧节点,尾指针前移
- 旧尾新头对比: 交叉对比,旧尾新头,如果找到,把新节点patch到旧节点,旧尾指针前移,新头指针后移
- 旧头新尾对比: 交叉对比,旧头新尾,如果找到,把新节点patch到旧节点,新尾指针前移,旧头指针后移
- 利用key对比: 用新指针对应节点的key去旧数组寻找对应的节点,这里分三种情况,当没有对应的key,那么创建新的节点,如果有key并且是相同的节点,把新节点patch到旧节点,如果有key但是不是相同的节点,则创建新节点
React生命周期钩子函数有哪些?数据存储放在那个生命周期函数?
总体来说:
- 挂载时调用constructor,更新时不调用
- 更新时调用
shouldComponentUpdate
和getSnapshotBeforeUpdate
,挂载时不调用 shouldComponentUpdate
在render前调用,getSnapshotBeforeUpdate
在render后调用- 请求放在
componnetDidMount
里。原因是:更新时触发的生命周期函数会调用多次,可能会触发多余调用或无限调用;此外constructor会在ssr时调用,所以不能放ajax请求;卸载时调用是毫无意义的。
React如何实现组件间通讯?如何理解redux?
-
父子组件通信:props + 函数
-
爷孙组件通信:两层父子通信或者使用
Context.Provider
和Context.Consumer
-
任意组件通信: 状态管理
a. Redux
b. Mobx
c. Recoil
Redux
-
Redux是一个状态管理库/状态容器
-
Redux的核心概念:
a. State
b. Action = type + payload 荷载
c. Reducer
d. Dispatch 后面接Action——派发
e. Middleware
-
ReactRedux的核心概念:
a. connect()
b. mapStateToProps
c. mapDispatchtoProps
- 常见中间件 redux-thunk redux-promise
什么是高阶组件HOC
参数是组件,返回值也是组件的函数。(抽象问题)
举例说明:
React.forwardRef
- ReactRedux的connect
- ReactRouter的withRouter
要想持久化保存这个button需要ref。
React Hooks 如何模拟组件生命周期
componentDidMount
componentDidUpdate
componentWillUnmount