You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
returnReact.createElement('div',{className: 'cn'},React.createElement(Header,null,'Hello, This is React'),React.createElement('div',null,'Start to learn right now!'),'Right Reserve',);
对比 render 函数被调用的时候,会返回的 tree 对象,复杂结构会在 children 中递归生成
{type: 'div',props: {className: 'cn',children: [{type: functionHeader,props: {children: 'Hello, This is React'}},{type: 'div',props: {children: 'start to learn right now!'}},'Right Reserve']}}
// 第一次渲染ReactDOM.render(<dialog><input/></dialog>,domContainer,);// 下一次渲染ReactDOM.render(<dialog><p>I was just added here!</p><input/></dialog>,domContainer,);
input → p :能重用宿主实例吗?不能,类型改变了! 需要删除已有的 input 然后重新创建一个 p 宿主实例。
(nothing) → input :需要重新创建一个 input 宿主实例。
因此,React 会像这样执行更新:
letoldInputNode=dialogNode.firstChild;dialogNode.removeChild(oldInputNode);letpNode=document.createElement('p');pNode.textContent='I was just added here!';dialogNode.appendChild(pNode);letnewInputNode=document.createElement('input');dialogNode.appendChild(newInputNode);
functionForm({ showMessage }){letmessage=null;// 占位用if(showMessage){message=<p>I was just added here!</p>;}return(<dialog>{message}<input/></dialog>);}
letinputNode=dialogNode.firstChild;letpNode=document.createElement('p');pNode.textContent='I was just added here!';dialogNode.insertBefore(pNode,inputNode);
functionShoppingList({ list }){return(<form>{list.map(item=>(<pkey={item.productId}>
You bought {item.name}<br/>
Enter how many do you want: <input/></p>))}</form>);}
React 内部是如何工作的 ?
Virtual DOM
1. React 元素
在浏览器环境(宿主环境)中,一个 DOM 节点宿(宿主实例)是最小的构建单元。而在 React 中,最小的构建单元是 React 元素。
React 元素是一个普通的 JavaScript 对象。它用来描述 DOM 节点。
2. jsx 生成 tree
中间过程经过 babel 编译,
createElement
的参数有三个:对比 render 函数被调用的时候,会返回的 tree 对象,复杂结构会在 children 中递归生成
我们来观察一下这个对象的 children,现在有三种类型:
除了这三种,还有两种类型:
3. 递归形渲染过程
由内到外递归渲染
它会像这样执行:
ReactDOM.render(<App />, domContainer)
App
,你想要渲染什么?App
:我要渲染包含<Content>
的<Layout>
。<Layout>
,你要渲染什么?Layout
:我要在<div>
中渲染我的子元素。我的子元素是<Content>
所以我猜它应该渲染到<div>
中去。<Content>
,你要渲染什么?<Content>
:我要在<article>
中渲染一些文本和<Footer>
。<Footer>
,你要渲染什么?<Footer>
:我要渲染含有文本的<footer>
。这就是为什么我们说协调是递归式的。当 React 遍历整个元素树时,可能会遇到元素的 type 是一个组件。React 会调用它然后继续沿着返回的 React 元素下行(children)。最终我们会调用完所有的组件,然后 React 就会知道该如何改变 DOM 树。
diff 算法
React 的 render 方法,它能将虚拟 DOM 渲染成真正的 DOM。为了减少 DOM 更新数量,我们需要找渲染前后真正变化的部分,只更新这一部分 DOM。而对比变化,找出需要更新部分的算法我们称之为 diff 算法。React 框架选择直接对比虚拟 DOM 和真实 DOM,这样就不需要额外保存上一次渲染的虚拟 DOM,并且能够一边对比一边更新。
不管是 DOM 还是虚拟 DOM,它们的结构都是一棵树,完全对比两棵树变化的算法时间复杂度是 O(n^3),但是考虑到我们很少会跨层级移动 DOM,所以我们只需要对比同一层级的变化。
传统 diff 算法
React Diff
综上所述, diff 算法有两个原则:
实现
diff 方法,它的作用是对比真实 DOM 和虚拟 DOM,最后返回更新后的 DOM
1. tree diff
tree 是由 众多 component 组件构成 ,React 对树的同一层级进行比较,当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。然后继续对树进行递归遍历,去比较 component。
当 React 节点同一层级根节点不一致(也就是发生跨层级的移动操作),React diff 会只有创建和删除操作,将创建新的节点变化的原节点销毁。
在这个例子中,
<input>
宿主实例会被重新创建。React 会遍历整个元素树,并将其与先前的版本进行比较:dialog → dialog
:能重用宿主实例吗?能 — 因为类型是匹配的。input → p
:能重用宿主实例吗?不能,类型改变了! 需要删除已有的input
然后重新创建一个p
宿主实例。(nothing) → input
:需要重新创建一个input
宿主实例。因此,React 会像这样执行更新:
2.component diff
有以下 3 个比较策略:
不管
showMessage
是true
还是false
,在渲染的过程中<input>
总是在第二个孩子的位置且不会改变。如果
showMessage
从false
改变为true
,React 会遍历整个元素树,并与之前的版本进行比较:dialog → dialog
:能够重用宿主实例吗?能 — 因为类型匹配。(null) → p
:需要插入一个新的p
宿主实例。input → input
:能够重用宿主实例吗?能 — 因为类型匹配。之后 React 大致会像这样执行代码:
3. element diff
比较树中同一位置的元素类型对于是否该重用还是重建相应的宿主实例往往已经足够。
但这只适用于当子元素是静止的并且不会重排序的情况。在上面的例子中,即使
message
不存在,我们仍然知道输入框在消息之后,并且再没有其他的子元素。而当遇到动态列表时,我们不能确定其中的顺序总是一成不变的。
当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。
React 允许开发者对于这一层级的同组子节点,添加唯一
key
进行区分,提高 diff 性能,避免卸载后又再次创建的操作出现。key
给予 React 判断子元素是否真正相同的能力,即使在渲染前后它在父元素中的位置不是相同的。给
key
赋予什么值最好呢?最好的答案就是:什么时候你会说一个元素不会改变即使它在父元素中的顺序被改变? 例如,在我们的商品列表中,商品本身的 ID 是区别于其他商品的唯一标识,那么它就最适合作为 key 。fiber 架构
React 16 之前的 diff 阶段的比较是不可被打断,React16 由主线程不间断使用 Diff(同步比较 + 同步更新) 变为 自由释放主线程(可打断的比较 + 异步更新)可以被打断的新的 fiber 架构。
参考
The text was updated successfully, but these errors were encountered: