面試官:UseSelector 返回的一個(gè)State數(shù)據(jù)很大,這個(gè)要怎么優(yōu)化
如果 useSelector 返回的 state 數(shù)據(jù)很大,可能會(huì)導(dǎo)致 不必要的組件重新渲染,影響性能。優(yōu)化方式主要有以下幾種:
1. 精確選擇數(shù)據(jù),避免返回整個(gè)對(duì)象
? 錯(cuò)誤做法(返回整個(gè) state 對(duì)象)
const bigData = useSelector(state => state.bigData);
?? 問題:
- 任何 state.bigData 內(nèi)部的字段變化都會(huì)觸發(fā)組件重新渲染,即使組件只用到其中一部分?jǐn)?shù)據(jù)。
? 正確做法(只選擇需要的字段)
const someValue = useSelector(state => state.bigData.someValue);
? 優(yōu)勢(shì):
- 組件只會(huì)在 someValue 變化時(shí)重新渲染,而不會(huì)因 bigData 的其他字段變化而重新渲染。
2. 使用 reselect 進(jìn)行 Memoization
如果計(jì)算 state 需要進(jìn)行復(fù)雜的計(jì)算(如 filter、map 等),可以使用 reselect 緩存計(jì)算結(jié)果。
?? 安裝 reselect
npm install reselect
?? 創(chuàng)建 selector
import { createSelector } from "reselect";
// 原始數(shù)據(jù)選擇器
const selectBigData = state => state.bigData;
// 計(jì)算派生數(shù)據(jù)
export const selectFilteredData = createSelector(
[selectBigData],
bigData => bigData.filter(item => item.active) // 只返回 active 狀態(tài)的數(shù)據(jù)
);
?? 組件中使用 selector
import { useSelector } from "react-redux";
import { selectFilteredData } from "./selectors";
const MyComponent = () => {
const filteredData = useSelector(selectFilteredData);
return <div>{filteredData.length}</div>;
};
? 優(yōu)勢(shì):
- createSelector 只有在 state.bigData 變化時(shí)才會(huì) 重新計(jì)算,否則返回緩存結(jié)果。
- 避免不必要的計(jì)算,提升性能。
3. useSelector 第二個(gè)參數(shù) equalityFn 自定義比較
默認(rèn)情況下,useSelector 使用 淺比較(===) 來判斷狀態(tài)是否變化。如果 state 是 深層對(duì)象,可以使用 自定義比較函數(shù) 進(jìn)行優(yōu)化。
?? 默認(rèn) useSelector(淺比較,可能會(huì)導(dǎo)致不必要的渲染)
const data = useSelector(state => state.bigData); // 任何字段變化都會(huì)重新渲染
?? 使用 shallowEqual 進(jìn)行淺層對(duì)比
import { shallowEqual, useSelector } from "react-redux";
const data = useSelector(state => state.bigData, shallowEqual);
? 優(yōu)勢(shì):
- shallowEqual 僅在 bigData 的 頂層字段 發(fā)生變化時(shí)才觸發(fā)組件重新渲染。
?? 自定義 equalityFn(僅在 id 變化時(shí)更新)
const selectedItem = useSelector(
state => state.bigData.find(item => item.id === 1),
(prev, next) => prev.id === next.id // 只在 id 變化時(shí)重新渲染
);
? 優(yōu)勢(shì):
- 避免 bigData 其他字段變化時(shí),導(dǎo)致不必要的重新渲染。
4. 拆分 state,減少 store 更新影響范圍
如果 bigData 很大,可以考慮拆分 store 結(jié)構(gòu),讓 Redux 多個(gè) slice 管理不同部分的數(shù)據(jù)。
?? 優(yōu)化前(單個(gè) slice 存放大量數(shù)據(jù))
const rootReducer = combineReducers({
bigData: bigDataReducer
});
?? 優(yōu)化后(拆分成多個(gè) slice)
const rootReducer = combineReducers({
users: usersReducer,
products: productsReducer
});
?? 組件按需獲取 state
const users = useSelector(state => state.users);
const products = useSelector(state => state.products);
? 優(yōu)勢(shì):
- 避免 bigData 變化時(shí),所有依賴 bigData 的組件都重新渲染。
5. 組件拆分,減少渲染范圍
如果 useSelector 獲取的數(shù)據(jù)很大,考慮 拆分組件,讓子組件只訂閱需要的數(shù)據(jù)。
?? 錯(cuò)誤示例(整個(gè)組件訂閱大數(shù)據(jù))
const ParentComponent = () => {
const bigData = useSelector(state => state.bigData);
return (
<div>
{bigData.map(item => (
<p key={item.id}>{item.name}</p>
))}
</div>
);
};
?? 優(yōu)化示例(拆分成子組件,每個(gè)子組件獨(dú)立訂閱狀態(tài))
const ListItem = ({ id }) => {
const item = useSelector(state => state.bigData.find(i => i.id === id));
return <p>{item.name}</p>;
};
const ParentComponent = () => {
const itemIds = useSelector(state => state.bigData.map(i => i.id));
return (
<div>
{itemIds.map(id => (
<ListItem key={id} id={id} />
))}
</div>
);
};
? 優(yōu)勢(shì):
- 只有受影響的 ListItem 組件會(huì)重新渲染,而不是整個(gè) ParentComponent。
6. 結(jié)合 useMemo 進(jìn)行優(yōu)化
如果 useSelector 返回的數(shù)據(jù)需要復(fù)雜計(jì)算,可以用 useMemo 緩存結(jié)果,避免重復(fù)計(jì)算。
?? 錯(cuò)誤示例(計(jì)算邏輯直接在 useSelector 內(nèi)部)
const filteredData = useSelector(state =>
state.bigData.filter(item => item.active) // 每次都會(huì)重新計(jì)算
);
?? 優(yōu)化示例(用 useMemo 緩存計(jì)算結(jié)果)
const data = useSelector(state => state.bigData);
const filteredData = useMemo(() => data.filter(item => item.active), [data]);
? 優(yōu)勢(shì):
- useMemo 只有在 data 變化時(shí)才會(huì)重新計(jì)算,提高性能。
?? 總結(jié)
優(yōu)化方法 | 思路 | 適用場(chǎng)景 |
1. 精確選擇數(shù)據(jù) |
只返回 需要的字段,而不是整個(gè) | 適用于 |
2. 使用 |
緩存計(jì)算結(jié)果,避免重復(fù)計(jì)算 | 適用于 依賴計(jì)算、列表過濾 場(chǎng)景 |
3. 自定義 | 只在 關(guān)鍵數(shù)據(jù)變化 時(shí)觸發(fā)渲染 | 適用于 深層數(shù)據(jù)結(jié)構(gòu) |
4. 拆分 | 將 | 適用于 大規(guī)模應(yīng)用 |
5. 組件拆分 | 讓子組件獨(dú)立訂閱 | 適用于 列表渲染、大數(shù)據(jù)量場(chǎng)景 |
6. | 避免 | 適用于 復(fù)雜計(jì)算 |
?? 最終優(yōu)化思路:
- 盡量讓 useSelector 只選擇最小數(shù)據(jù)。
- 使用 reselect 避免不必要的計(jì)算。
- 盡量拆分組件,減少不必要的渲染。
這樣可以讓 react-redux 更高效地更新 UI,提升應(yīng)用性能! ??