前言
之前開發重構項目的時候,遇到了一些問題, 如 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
富文本的時候, 會經常出現重複渲染導致編輯器加載出現問題的場景, 報錯如圖:
一般來說都是因爲 quill
的 modules
對象指向改變了, 這一點在 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}/>
)
}
在 react
的 hooks
中, 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
的使用可以保證對象指向從而解決該問題,保證編輯器穩定可靠。
在開發中遇到問題是常見的,需要及時記錄和解決,提高開發效率和質量。