React 性能 debug 小記

前言

之前開發重構項目的時候,遇到了一些問題, 如 hooks 的性能問題和 quill 的重載問題。本文就是記錄這些問題的解決過程。

場景

在基於富文本的輸入場景中,我們發現在輸入回車後會出現明顯的卡頓現象。爲了更好地展示此類場景,這裏使用了一個簡單的例子展示。

function App() {
    const [value, setValue] = useState('');

    // mock 調用多次 hooks
    const hook1 = useHooks();
    //...
    const hook20 = useHooks();

    const modules = useMemo(() => ({
        toolbar: {
            container: '#toolbar',
            handlers: {
            },
        },
    }), []);
    

    return (<div className={'container'}>
        <CustomToolbar/>
        <ReactQuill ref={editorRef} theme="snow" value={value} modules={modules} onChange={setValue}/>
        <form className="todo-list">
            {/* ... */}
        </form>
    </div>)
}

這是頁面的主要結構, 內容分別是一堆 hooks + quill + 其他操作(這裏用一個 TODO list 來替代)

性能監控

既然是卡頓,那當然屬於性能方面的問題 ,這裏就輪到 Chrome 的性能監控出場了: 重複步驟, 最後縮短監控範圍, 點擊卡頓的任務

image

再來看下調用樹:

image

很明顯, 光看這裏很難發現是頁面那個地方的問題, 都是 react 的源碼執行函數

最關鍵的點在於自上而下那一欄:

image

從這裏, 我們很明顯的就能看到是這個 hooks - useI18n 影響到了

因爲這是一個多語言 hooks, 所以它的引用範圍特別廣

因爲不方便透露源碼, 大概的邏輯是這樣的:

const useHooks = () => {
  const {lang, handle:langHandle} = useContext(myContext);
  
  const handle = (number) => {
    langHandle();
  }

  return {
    value: lang,
    setValue: handle
  }
}

解決方案

這裏我嘗試加上 react 的性能優化 useMemo:

const useHooks = () => {
  const {lang, handle:langHandle} = useContext(myContext);
  
  const handle = (number) => {
    langHandle();
  }

  return useMemo(() => ({
    value: lang,
    setValue: handle
  }), [lang, handle])
}

再通使用 Chrome 的性能監控, 發現問題已經緩解

image

由此可得出結論, 在多場景使用的 hooks 中, 可通過在返回值中加上 useMemo 來提高性能


當然, 除了 hooks 的優化, 阻止其他組件的重渲染, 也可以緩解一定的渲染性能問題

這裏又回到了我們老生常談的 react 性能優化那一套, 就不贅述了

quill.js 的重渲染

function 組件中添加 quill.js 富文本的時候, 會經常出現重複渲染導致編輯器加載出現問題的場景, 報錯如圖:

image

一般來說都是因爲 quillmodules 對象指向改變了, 這一點在 hooks 組件中會經常遇到:

function App(){
  const modules = {
    toolbar: {
      container: '#toolbar',
      handlers: {
        handleClick
      },
    },
  }
  
  return (
    <ReactQuill ref={editorRef} theme="snow" value={value} modules={modules} onChange={setValue}/>
  )
}

如上述的代碼, 由於 react 的機制問題, 在每次 render 時, 都會觸發 reRender, 重新聲明一個 modules, 造成 react-quill 中的傳值問題

常見的解決方案就是萬能的 useRef 了:

function App(){
  const modulesRef = useRef({
    toolbar: {
      container: '#toolbar',
      handlers: {
        handleClick
      },
    },
  })
  
  return (
    <ReactQuill ref={editorRef} theme="snow" value={value} modules={modulesRef.current} onChange={setValue}/>
  )
}

reacthooks 中, useRef 反而是一個比較 OOP 的函數, 因爲設置之後, 不管 render 幾次, 他的對象引用都不會變化;

就像是 class 中構造函數裏設置了 this.query = {}, 在 render 過程中 query 值引用都是不變的

由此, 很多自定義 hooks, 都用上了他

比如最常見的 useUpdateEffect:

const createUpdateEffect = (hook) => (effect, deps) => {
    const isMounted = useRef(false);

    // for react-refresh
    hook(() => {
      return () => {
        isMounted.current = false;
      };
    }, []);

    hook(() => {
      if (!isMounted.current) {
        isMounted.current = true;
      } else {
        return effect();
      }
    }, deps);
  };



useUpdateEffect = createUpdateEffect(useEffect)

代碼來源於 ahooks, 使用 ref 的值來標記一個私有化值

原理都是很簡單的,但是在開發中想要得心應手,還需要更多的練習。

總結

使用 hooks 進行組件開發時,頻繁更新會影響性能。
通過優化代碼避免無用渲染,提高組件性能。
在使用 quill 編輯器時,重載頁面或組件會導致編輯內容丟失。
通過 useRef 的使用可以保證對象指向從而解決該問題,保證編輯器穩定可靠。
在開發中遇到問題是常見的,需要及時記錄和解決,提高開發效率和質量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章