內部小組分享底稿.
回顧一下 React
- class 組件的優化
- useMemo 提供的優化
- React.memo 優化
- useCallback 優化
- 避免 render 當中的 DOM 操作
class 組件的優化
通過判斷減少數據變化觸發的重新渲染, 以及之後的 DOM diff
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
JavaScript 對象引用問題
函數式語言當中, 語言設計允許兩個對象一樣, 舉例 Clojure:
(= {:a 1} {:a 1}) ; true
(identical? {:a 1} {:a 1}) ; false
遞歸匹配, 性能並不高.
JavaScript 對象基於引用傳值, 比較單一
{a: 1} === {a: 1} // false
大體方案, 通過手動維護, 讓相同的數據儘量保證引用一致, 控制性能.
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
useMemo 優化
每個函數體當中生成的對象都會有新的引用, useMemo
可以保留一致的引用.
const myObject = useMemo(() => ({ key: "value" }), [])
注意: 用花括號直接寫對象基本上就是新的引用了,
{}
{a: 1}
{...obj}
一般組件內部不變的對象, 都是從 state, ref, 再或者組件外全局有一個引用.
React.memo 優化
判斷參數是否改變, 如果沒有改變, 就直接複用已有的組件, 不重新生成:
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
React.memo
有第二個參數, 用於自定義判斷的規則:
const MemoItem = React.memo(Item, (prevProps, nextProps) => {
if (prevProps.item.selected === nextProps.item.selected) {
return true;
}
return false;
});
useCallback 優化
使用 React.memo
包裹組件:
let Inner: FC<{
onClick: () => void
}> = React.memo((props) => {
return <div>
<span>inner</span>
</div>;
});
使用 useCallback
let Outer: FC<{}> = React.memo((props) => {
const [counter, setCounter] = useState(0);
const onClick = useCallback(()=>{
setCounter(prevState => ++prevState)
},[]);
return <div>
<span>outer: {counter}</span>
<Inner onClick={onClick} />
</div>;
});
避免 render 當中的 DOM 操作
let NewComponent: FC<{}> = React.memo((props) => {
let elRef = useRef<HTMLDivElement>()
// 錯誤寫法
if (elRef.current) {
elRef.current.style.color = 'red'
}
return <div ref={elRef}></div>;
});
DOM 發生改變的時候, 一般會有比較多後續的佈局和 compose 計算去繪製新的界面.
特別是在腳本執行過程當中發生的話, 會對性能有明顯影響.
腳本執行完再執行, 讓瀏覽器自動處理(合併, 避免頻繁 DOM 操作).
業務相關
- immer 對優化方案的影響
- Rex 組件當中優化的坑
- 路由相關的優化
- 性能調試
Immer 對優化方案的影響
let a = {}
let b = produce(a, draft => {
draft.b = 1
})
a === b // false
如果數據不發生改變, 直接用原始數據.
(Hooks API 之後, 數據被拆散了, 可以減少 immer 的使用.)
Rex 當中優化的相關
class 組件, 高階組件當中自動做了基礎的優化.
shouldComponentUpdate(nextProps: IRexDataLayerProps, nextState: any) {
if (!shallowequal(nextProps.parentProps, this.props.parentProps)) return true;
if (!shallowequal(nextProps.computedProps, this.props.computedProps)) return true;
return false;
}
Hook API, 沒有中間一層組件, 直接觸發當前組件更新, 存在性能問題.(還要考慮優化方案)
let contextData = useRexContext((store: IGlobalStore) => {
return {
data: store.data,
homeData: store.homeData,
};
});
業務當中一般可以接受, 因爲數據通常都是在更新的. 新能敏感場景需要額外考慮.
ruled-router 提供的優化
/home/plant/123/shop/456/789
解析爲
{
"raw": "home",
"name": "home",
"matches": true,
"restPath": ["plant", "123", "shop", "456", "789"],
"params": {},
"data": {},
"next": {
"raw": "plant/:plantId",
"name": "plant",
"matches": true,
"restPath": ["shop", "456", "789"],
"params": {
"plantId": "123"
},
"data": {
"plantId": "123"
},
"next": {
"raw": "shop/:shopId/:corner",
"name": "shop",
"matches": true,
"next": null,
"restPath": [],
"data": {
"shopId": "456",
"corner": "789"
},
"params": {
"plantId": "123",
"shopId": "456",
"corner": "789"
}
}
}
}
生成對象保存起來, 路由發生變更時再重新解析. 這樣對象引用一般保持一致.
性能優調試
DevTools
https://developers.google.com...
React DevTools
https://www.debugbear.com/blo...
其他
官方推薦性能優化方案...
https://reactjs.org/docs/opti...
實際遇到
樹形組件: 隱藏子樹, 定製減少更新. (個人建議看情況自己實現, 通用組件一般都不好優化).
略
useMemo
略
Dropdown 的替換, 老版本 antd 的 bug(升級 [email protected]
).
略
https://github.com/react-comp...
需要優化
- form
- table
- ...