React?Diffing?算法完整指南(示例詳解)
React Diffing 算法完整指南
1. Diffing 算法概述
1.1 什么是 Diffing
Diffing 算法是 React 用于比較兩棵虛擬 DOM 樹(shù)差異的算法,用來(lái)確定需要更新的部分,從而最小化 DOM 操作。
1.2 基本原則
- 不同類(lèi)型的元素會(huì)產(chǎn)生不同的樹(shù)
- 通過(guò) key 屬性標(biāo)識(shí)哪些子元素在不同渲染中保持穩(wěn)定
- 采用同層比較策略
2. Diffing 策略詳解
2.1 元素類(lèi)型比較
// 不同類(lèi)型元素比較 // 舊樹(shù) <div> <Counter /> </div> // 新樹(shù) <span> <Counter /> </span> // React 會(huì)完全刪除舊樹(shù),重建新樹(shù)
2.2 同類(lèi)型元素比較
// 同類(lèi)型DOM元素比較 // 舊樹(shù) <div className="old" title="old"> Hello </div> // 新樹(shù) <div className="new" title="new"> World </div> // React 只會(huì)更新變化的屬性
2.3 組件比較
class MyComponent extends React.Component {
render() {
// 更新時(shí)只比較渲染結(jié)果
return (
<div>
<h1>{this.props.title}</h1>
<p>{this.props.content}</p>
</div>
);
}
}3. 列表 Diffing
3.1 無(wú) key 的情況
// 效率較低的列表渲染
function ListWithoutKeys() {
return (
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
);
}
// 當(dāng)列表項(xiàng)變化時(shí),React 需要重新渲染所有項(xiàng)3.2 使用 key 的優(yōu)化
// 使用 key 的列表渲染
function ListWithKeys() {
const items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
];
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
// React 可以通過(guò) key 識(shí)別哪些元素保持不變3.3 key 的最佳實(shí)踐
// 不推薦:使用索引作為 key
const BadList = () => (
<ul>
{items.map((item, index) => (
<li key={index}>{item.text}</li>
))}
</ul>
);
// 推薦:使用穩(wěn)定的唯一標(biāo)識(shí)作為 key
const GoodList = () => (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);4. Diffing 算法實(shí)現(xiàn)原理
4.1 樹(shù)的遍歷策略
function diffTree(oldTree, newTree) {
if (oldTree === null) {
// 插入新節(jié)點(diǎn)
return createNode(newTree);
}
if (newTree === null) {
// 刪除舊節(jié)點(diǎn)
return null;
}
if (oldTree.type !== newTree.type) {
// 替換節(jié)點(diǎn)
return createNode(newTree);
}
// 更新現(xiàn)有節(jié)點(diǎn)
updateNode(oldTree, newTree);
// 遞歸處理子節(jié)點(diǎn)
diffChildren(oldTree.children, newTree.children);
}4.2 子節(jié)點(diǎn)比較算法
function diffChildren(oldChildren, newChildren) {
// 第一輪:處理更新的節(jié)點(diǎn)
for (let i = 0; i < Math.min(oldChildren.length, newChildren.length); i++) {
diff(oldChildren[i], newChildren[i]);
}
// 處理新增的節(jié)點(diǎn)
if (newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach(child => {
create(child);
});
}
// 處理刪除的節(jié)點(diǎn)
if (oldChildren.length > newChildren.length) {
oldChildren.slice(newChildren.length).forEach(child => {
remove(child);
});
}
}5. 性能優(yōu)化策略
5.1 避免不必要的渲染
class OptimizedComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只在必要時(shí)更新
return this.props.value !== nextProps.value;
}
render() {
return <div>{this.props.value}</div>;
}
}
// 使用 React.memo 優(yōu)化函數(shù)組件
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});5.2 列表優(yōu)化
// 使用 key 和 memo 優(yōu)化列表渲染
const OptimizedListItem = React.memo(({ item }) => (
<li>{item.text}</li>
));
function OptimizedList({ items }) {
return (
<ul>
{items.map(item => (
<OptimizedListItem
key={item.id}
item={item}
/>
))}
</ul>
);
}5.3 大型列表虛擬化
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].text}
</div>
);
return (
<FixedSizeList
height={400}
width={300}
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}6. 常見(jiàn)問(wèn)題和解決方案
6.1 key 相關(guān)問(wèn)題
// 問(wèn)題:key 不穩(wěn)定導(dǎo)致的重新渲染
const ProblematicList = () => (
<ul>
{items.map((item, i) => (
<li key={Math.random()}>{item.text}</li> // 不要這樣做
))}
</ul>
);
// 解決方案:使用穩(wěn)定的唯一標(biāo)識(shí)
const FixedList = () => (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);6.2 不必要的重渲染
// 問(wèn)題:父組件更新導(dǎo)致子組件不必要的重渲染
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child data={data} /> // 即使 data 沒(méi)變,Child 也會(huì)重渲染
</div>
);
};
// 解決方案:使用 useMemo 或 React.memo
const Parent = () => {
const [count, setCount] = useState(0);
const memoizedData = useMemo(() => data, [data]);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child data={memoizedData} />
</div>
);
};7. 總結(jié)
7.1 Diffing 算法要點(diǎn)
- 采用同層比較策略
- 不同類(lèi)型元素產(chǎn)生不同樹(shù)
- key 屬性的重要性
- 組件的穩(wěn)定性
7.2 優(yōu)化建議
- 合理使用 key
- 避免不必要的嵌套
- 使用不可變數(shù)據(jù)結(jié)構(gòu)
- 適當(dāng)使用 memo 和 useMemo
- 大列表考慮虛擬化
7.3 最佳實(shí)踐
- 保持組件的純粹性
- 合理拆分組件
- 正確使用 key
- 避免深層組件樹(shù)
- 及時(shí)進(jìn)行性能優(yōu)化
8. 經(jīng)典面試題
1.react/vue中的key有什么作用? (key的內(nèi)部原理是什么?)
2.為什么遍歷列表時(shí),key最好不要用index?
1. 虛擬DOM中key的作用:
簡(jiǎn)單的說(shuō):key是虛擬DOM對(duì)象的標(biāo)識(shí),在更新顯示時(shí)key起著極其重要的作用。
詳細(xì)的說(shuō):當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化時(shí),react會(huì)根據(jù)【新數(shù)據(jù)]生成[新的虛擬DOM], 隨后React進(jìn)行【新虛擬DOM]與【舊虛擬DOM]的diff 比較,比較規(guī)則如下:
a,舊虛擬DOM中找到了與新虛擬DOM相同的key:
(1).若虛擬DOM中內(nèi)容沒(méi)變,直接使用之前的真實(shí)DOM
(2).若虛擬DOM中內(nèi)容變了,則生成新的真實(shí)DOM,隨后替換掉頁(yè)面中之前的真實(shí)DOM
b.舊虛擬DOM中未找到與新虛擬DOM相同的key 根據(jù)數(shù)據(jù)創(chuàng)建新的真實(shí)DOM,隨后渲染到到頁(yè)面
2.用indexf作 key可能會(huì)引發(fā)的問(wèn)題:
1.若對(duì)數(shù)據(jù)進(jìn)行:逆序添加,逆序刪除等破壞順序操作: 會(huì)產(chǎn)生沒(méi)有必要的真實(shí)DOM更新 ==>界面效果沒(méi)問(wèn)題,但效半低。
2.如果結(jié)構(gòu)中還包含輸入類(lèi)的DOM: 會(huì)產(chǎn)生錯(cuò)誤DOM更新 ==>界面有問(wèn)題。
3.注意!如果不存在對(duì)數(shù)據(jù)的逆序添加、逆序刪除等破壞順序操作,僅用于渲染列表用于展示,使用index作為key是沒(méi)有問(wèn)題的。
3.開(kāi)發(fā)中如何選擇key?:
1.最好使用每條數(shù)據(jù)的唯一標(biāo)識(shí)作為key,比如id、手機(jī)號(hào)、身份證號(hào)、學(xué)號(hào)等唯一值。
2.如果確定只是簡(jiǎn)單的展示數(shù)據(jù),用index也是可以的。
到此這篇關(guān)于React Diffing 算法完整指南的文章就介紹到這了,更多相關(guān)React Diffing 算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-router browserHistory刷新頁(yè)面404問(wèn)題解決方法
本篇文章主要介紹了react-router browserHistory刷新頁(yè)面404問(wèn)題解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-12-12
React Hooks獲取數(shù)據(jù)實(shí)現(xiàn)方法介紹
這篇文章主要介紹了react hooks獲取數(shù)據(jù),文中給大家介紹了useState dispatch函數(shù)如何與其使用的Function Component進(jìn)行綁定,實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
React實(shí)現(xiàn)生成和導(dǎo)出Word文檔的方法詳解
React是一個(gè)流行的JavaScript庫(kù),用于構(gòu)建現(xiàn)代前端應(yīng)用程序,本文將深入探討如何在React中生成和導(dǎo)出Word文檔,感興趣的小伙伴可以學(xué)習(xí)一下2023-09-09
react項(xiàng)目打包后點(diǎn)擊index.html頁(yè)面出現(xiàn)空白的問(wèn)題
這篇文章主要介紹了react項(xiàng)目打包后點(diǎn)擊index.html頁(yè)面出現(xiàn)空白的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06

