陳Ken
一位熱愛爬山的前端工程師
React 讓我們可以更專注在元件開發、狀態改變後的畫面該長什麼樣子 (What)
而不用去思考、關注畫面怎麼更新、改變(How),讓我們可以快速開發 Web App
const container = document.getElementById(‘container’);
const btn = document.createElement(‘button’);
btn.className = ‘btn red’;
btn.onclick = function(event) {
if (this.classList.contains(‘red’)) {
this.classList.remove(‘red’);
this.classList.add(‘blue’);
} else {
this.classList.remove(‘blue’);
this.classList.add(‘red’);
}
};
container.appendChild(btn);
const Container = (props) => {
const [color, setColor] = useState('red')
const handleChange = () => {
setColor(prevColor => (
prevColor === 'red'? 'blue' : 'red')
)
}
return (
<div>
<button
className=`btn ${color}`
onClick={handleChange}
>
點我
</button>
</div>
)
}
重視當下的值
及
接下來要怎麼操縱值
重視流程、結果
JSX 關注的是畫面
How
What
我們只要關注 What ,讓 React 來處理 How
但 React 如何更新我們的畫面?
Reconciliation 是 React 如何更新畫面的過程;
也有人直接將 React 的 Diff 的演算法稱作 Reconciliation
偵測到改變,vDom 準備開始比較差異
Dom 維持不變
Dom 維持不變
Dom 更新畫面
vDom
比較前後差異
vDom
更新差異到 Dom
舊 新 比較差異
將差異更新到畫面
render() 就會回傳一組新的 React Elements Tree
每當有 state 或 props 更新時...
React 需要比較 新 Tree 、舊 Tree 的差別
然後更新到 DOM
(也是 function component 的 return)
刪除 c
插入 c
f 改名 a
e 改名 d
x
c
a
d
3 個 節點,27 個單位時間
100 個 節點,10,00,000 個單位時間
我有個 100 節點的 html ,
我每次狀態更新的比較要花 100 萬個工作時間?!
stackoverflow : if n^3 has a fast rate of ...
所以 React 透過兩個假設下,實作了一個
的啓發式 (heuristic) 演算法。
假設1:兩個不同類型的 element 會產生出不同的 tree
不會再往下進行比較了~
直接更新!
假設2:稍後揭曉
React 如何比較樹的差異?
stackoverflow : if n^3 has a fast rate of ...
在 React 提到的啟發式(heuristic) 演算法,通常就是指virtual DOM diff的演算法。
heuristic,有種經驗法則的意思在!
在實務上,只要你能證明你的演算法效率良好且可拿到最佳解或次佳解的話。
都可以稱之為啟發式演算法
比如 A*搜尋演算法、雁群(PSO)演算法等
在機器學習跟人工智慧是很常探討到的研究課題之一。
1. 不同的 Element Type
round1 render: <div />
round2 render: <span />
=> [移除 <div />], [插入 <span />]
<div>
round1 render: <Counter />
</div>
<span>
round2 render: <Counter />
</span>
<Counter> 會被銷毀,並且在 round2 重建
ref - React cn doc Reconciliation(v0.14.3)
2. 相同 Type 的 React DOM Element
round1 render: <div className="before" title="stuff" />
round2 render: <div className="after" title="stuff" />
=> [取代 attribute className 為 "after"]
round1 render: <div style={{color: 'red'}} />
round2 render: <div style={{fontWeight: 'bold'}} />
=> [移除樣式 color], [新增樣式 font-weight 'bold']
這層比較完,接著遞迴往下檢查 children
3. 相同 Type 的 React Component Element
ref - React cn doc Reconciliation(v0.14.3)
當一個 component 更新時,該 component 的 instance .....
class 是一個模板,供我們快速建立相似的物件
object 就是 instance of class
class Person {
constructor(firstName,lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
eat() {
console.log('eat')
}
}
const ken = new Person('Ken','Chen')
const pj = new Person('PJ','Chen')
ken 這個物件是 Person 的 instance (實例)
pj 這個物件也是 Person 的 instance
3. 相同 Type 的 React Component Element
當一個 component 更新時,該 component 的 instance 保持不變,這樣 state 能夠被保留在不同次的 render 中。
儲存既有狀態
更新未來狀態
比較兩者差異、回傳新 Tree
React 會更新該 component instance 的 props 以跟最新的 element 保持一致,並且呼叫該 instance 的 UNSAFE_componentWillReceiveProps() 、 UNSAFE_componentWillUpdate() 和 componentDidUpdate() 方法。
接下來,該 instance 會再呼叫 render() 方法,而 diff 算法將會遞迴處理舊的結果以及新的結果。
4. 比較遞迴處理中的 Children
React.createElement(component, props, ...children)
4. 比較遞迴處理中的 Children
ref - React cn doc Reconciliation(v0.14.3)
round1 render: <ul><li>first</li></ul>
round2 render: <ul><li>first</li><li>second</li></ul>
=> [插入 <li>second</li>]
round1 render: <ul><li>first</li></ul>
round2 render: <ul><li>second</li><li>first</li></ul>
=> [取代 attribute textContent 為 "second"],
[插入 <li>first</li>]
keys
救星降臨:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>second</li>
<li>first</li>
</ul>
<ul>
<li>first</li>
</ul>
<ul>
<li>first</li>
</ul>
4. 比較遞迴處理中的 Children
ref - React cn doc Reconciliation(v0.14.3)
round1 render: <ul><li key='1st'>first</li></ul>
round2 render: <ul><li key='2nd'>second</li><li key='1st'>first</li></ul>
=> [插入 <li>second</li>]
所以 React 透過兩個假設下,實作了一個
的啓發式 (heuristic) 演算法。
假設1:兩個不同類型的 element 會產生出不同的 tree
假設2:開發者可以通過 key prop 來指出哪些 child element 在不同的 render 下能保持不變
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
{[<li>first</li>, <li>second</li>]}
</ul>
<ul>
{
list.map((todo,index)=>(
<li key={index}>
{todo}
</li>
))
}
</ul>
const [list, setList] = useState(['first','second'])
<ul>
<li key={0}>first</li>
<li key={1}>second</li>
</ul>
setList([...list].reverse())
<ul>
<li key={0}>second</li>
<li key={1}>first</li>
</ul>
round1 render: <ul><li>first</li><li>second</li></ul>
round2 render: <ul><li>second</li><li>first</li></ul>
=> [取代 attribute textContent 為 "second"],
[取代 attribute textContent 為 "first"]
假設你的 li 是 Custom Component, 裡面除了文字,還有些 state, props 或 refs?
Diff 演算法,用來比較 vitual DOM 前後差異
Reconciliation 是 React 如何更新畫面的過程;
也有人直接將 React 的 Diff 的演算法稱作 Reconciliation
假設1:兩個不同類型的 element 會產生出不同的 tree
假設2:開發者可以通過 key prop 來指出哪些 child element 在不同的 render 下能保持不變
heuristic 演算法 的 4 個比較原則及 2 個假設
1. 比較 Element Type
2. 相同的 React DOM Element Type 比較
3. 相同的 React Component Element Type 比較
4. 比較遞迴處理中的 Children
Reconciliation 只是 React 底層其中一個實作的細節
React 會基於前述規則進行差異合併
rerender 會在所有元件呼叫 render(),
但不代表 React 一定會 unmount, remount 它們
React 官方會定期改良 heuristic 演算法
但以現階段而言,ㄧ個 subTree 只能在 Siblings 中移動
只要往父層或子層移動,必重新 rerender
heuristic 演算法,基於前述 2 個假設
1. 演算法不會特別比較 subTree,若不斷在 2 元件中切換,且底下有很相似的內容,可能有效能問題
2. Key 應該具有穩定性、唯一性、可預測性,否則可能導致效能低落、子元件 state 遺失
1. 演算法不會特別比較 subTree,若不斷在 2 元件中切換,且底下有很相似的內容,可能有效能問題
<div>
round1 render: <Counter />
</div>
<span>
round2 render: <Counter />
</span>
<Counter> 會被銷毀,並且在 round2 重建
2. Key 應該具有穩定性、唯一性、可預測性,否則可能導致效能低落、子元件 state 遺失
round1 render: <ul><Li key="0">first</Li><Li key="1">second</Li></ul>
round2 render: <ul><Li key="0">second</Li><Li key="1">first</Li></ul>
=> [取代 attribute textContent 為 "second"],
[取代 attribute textContent 為 "first"]
By 陳Ken
A brief intro about the concept of React virtual DOM update. Find sharing at https://www.youtube.com/watch?v=jyd7UyAiwX4