博客 / 詳情

返回

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 的性能監控出場了: 重複步驟, 最後縮短監控範圍, 點擊卡頓的任務

再來看下調用樹:

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

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

從這裏, 我們很明顯的就能看到是這個 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 的性能監控, 發現問題已經緩解

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


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

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

quill.js 的重渲染

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

一般來説都是因為 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 的使用可以保證對象指向從而解決該問題,保證編輯器穩定可靠。
在開發中遇到問題是常見的,需要及時記錄和解決,提高開發效率和質量。

user avatar 1023 頭像 ivyzhang 頭像 pangsir8983 頭像 mrqueue 頭像 gfeteam 頭像 tofrankie 頭像 yangkaiqiang 頭像 513928731 頭像 lawler61 頭像 denzel 頭像 yxaw 頭像 carloslab 頭像
13 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.