網站首頁 編程語言 正文
前言
在我們使用useContext來進行數據流管理時,每當context更新時,所有使用到該context的組件都會重新渲染。如果我們的context的數據是由多個部分組成的,但只有其中一兩個字段會頻繁更新,但其他的數據都比較穩定時,這時,即使組件值使用到了比較穩定的那部分數據,但它依然會頻繁渲染,這就很容易會導致性能問題。我們一般會使用拆分context或者結合useMemo來減少組件渲染的次數:
1.拆分context
我們可以通過將context拆分為承載不穩定數據的instableContext和承載穩定數據的stableContext。
const InstableStateContext = React.createContext(); const StableStateContext = React.createContext(); function Provider({children}) { const [instableState, instableDispatch] = React.useState(); const [stableState, stableDispatch] = React.useState(); return ( <StableStateContext.Provider value={{state:stableState, dispatch:stableDispatch}}> <InstableStateContext.Provider value={{state:instableState, dispatch:instableDispatch}}> {children} </InstableStateContext.Provider> </StableStateContext.Provider> ) }
在只使用穩定數據的組件中,我們只去使用stableContext,
//stableComponent.js function stableComponent() { const {state} = React.useContext(StableStateContext); return ...; }
這能夠讓stableComponent.js只有在StableStateContext中的數據更新時,才會觸發渲染,而不需要關心InstableStateContext
2.使用useMemo包裹函數
useMemo可以傳入一個數據生成函數和依賴項,它可以使數據生成函數當且僅當依賴性發生變化時,才會重新計算要生成的數據的值。我們可以將組件的返回值使用useMemo進行包裹,把要使用的數據作為依賴項傳入
const {state}= useContext(AppContext); return useMemo(() => <span>data:{state.depData}</span>, [state.depData]);
在上面的例子中,當且僅當depData發生變化時,該組件才會重新渲染。
雖然上面兩種方法都可以減少一些不必要的渲染,但寫起來總覺得不夠優雅(很麻煩)。下面我們來講講另一種減少使用useContext導致的不必要渲染的方法。
使用發布訂閱減少使用useContext導致的不必要渲染
我們有沒有辦法做到只有在我們使用到的context數據發生變化時,才去觸發渲染,而不需要使用useMemo進行繁瑣的包裹呢。
我們可以創建這么一個store,它擁有一個getState方法可以用來獲取context中存儲的數據。
const [state, dispatch] = useReducer(this.reducer, initState); const store = { getState: () => state, dispatch, }
我們使用useMemo對store的值進行包裹,且deps為空數組:
const [state, dispatch] = useReducer(this.reducer, initState); const store =useMemo(() => ({ getState: () => state, dispatch, }),[]);
這樣store的值的引用便不會發生改變,如果把store作為context.Provider的value值進行傳遞:
Provider = (props: ProviderProps) => { const { children, initState = {} } = props; const [state, dispatch] = useReducer(this.reducer, initState); //store值不會更新,所以不會觸發渲染 const store = useMemo( () => ({ getState: () => cloneDeep(state), dispatch, }), [], ); return <this.context.Provider value={store}>{children}</this.context.Provider>; };
這樣Provider下的組件便不會因為state的變化而觸發渲染。但這樣的話,因為store的值沒有發生變化,provider內的組件便沒有辦法得知該何時去渲染了。這時我們引入發布訂閱模式,來通知組件該何時渲染。當state發生變化時,我們會觸發stageChange
事件:
Provider = (props: ProviderProps) => { const { children, initState = {} } = props; const [state, dispatch] = useReducer(this.reducer, initState); useEffect(() => { //告知useSelector,state已更新,讓它觸發forceUpdate this.emit('stateChange'); }, [state]); //store值不會更新,所以不會觸發渲染 const store = useMemo( () => ({ getState: () => cloneDeep(state), dispatch, }), [], ); return <this.context.Provider value={store}>{children}</this.context.Provider>;
在下面講到的useSelector中會訂閱此事件來告知組件需要重新渲染了。
接下來我們會實現一個useSelector方法,作為我們在組件內獲取state中的數據的橋梁,他接收一個selector函數作為參數,如:
const a = useSelector(state=>state.a)
這樣,我們就可以獲取到state中的a。接下來我們要做的就是如何使得當state.a更新時,組件能夠觸發渲染,同時獲取到最新的a。
上面說到,在useSelector中,我們會訂閱stageChange事件,這時,我們會檢查selector選中的數據有沒有發生變化,有的話便使用forceUpdate
進行強制渲染;
useSelector: UseSelector = (selector) => { const forceUpdate = useForceUpdate(); const store = useContext<any>(this.context); const latestSelector = useRef(selector); const latestSelectedState = useRef(selector(store.getState())); if (!store) { throw new Error('必須在Provider內使用useSelector'); } latestSelector.current = selector; latestSelectedState.current = selector(store.getState()); useEffect(() => { const checkForUpdates = () => { const newSelectedState = latestSelector.current(store.getState()); //state發生變化時,檢查當前selectedState和更新后的SelectedState是否一致,不一致則觸發渲染 if (!isEqual(newSelectedState, latestSelectedState.current)) { forceUpdate(); } }; this.on('stateChange', checkForUpdates); return () => { this.off('stateChange', checkForUpdates); }; }, [store]); return latestSelectedState.current; };
forceUpdate
的原理也很簡單,通過變更一個無用的狀態來觸發組件更新:
const useForceUpdate = () => { const [_, setState] = useState(false); return () => setState((val) => !val); };
就這樣,當我們在組件時使用useSelector時獲取數據時,只有在selector選中的數據被更新時,組件才會重新渲染。
原文鏈接:https://www.jianshu.com/p/4a41d04c8e48
相關推薦
- 2022-10-22 Python基礎Lists和tuple實例詳解_python
- 2023-10-11 在MyBatisPlus中添加分頁插件
- 2023-03-26 Redis?分片集群的實現_Redis
- 2021-11-08 深入解析golang中的標準庫flag_Golang
- 2022-05-14 InnoDB主鍵索引樹和二級索引樹的場景分析_數據庫其它
- 2022-10-14 Transformer解讀之:Transformer 中的 Attention 機制
- 2022-04-25 C#利用NPOI操作Excel(單元格設置)_C#教程
- 2022-12-09 PyTorch中關于tensor.repeat()的使用_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支