日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React實現控制減少useContext導致非必要的渲染詳解_React

作者:CBDxin ? 更新時間: 2022-12-10 編程語言

前言

在我們使用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

欄目分類
最近更新