多实例react-draft-wysiwyg编辑器UI更新卡顿,输入处理耗时过长求助
从你的描述和代码来看,卡顿的核心原因是高频输入触发的Redux全量更新+不必要的组件重渲染,再加上saga里的遍历数组等额外开销,导致input事件处理耗时超标。下面是针对性的优化方案:
一、优化Redux状态结构,避免全量替换
目前你用数组存储所有编辑器状态,每次更新都要复制整个数组,不仅开销大,还会导致所有Editor组件因为editors数组引用变化而触发重渲染。建议改成以编辑器name为key的对象结构:
修改后的状态结构:
// redux state { ... editors: { editor1: { name: 'editor1', label: 'Editor 1', state: /* draft-js editorState */ }, editor2: { name: 'editor2', label: 'Editor 2', state: /* draft-js editorState */ } } ... }
对应的Reducer修改:
case STORE_EDITOR_STATE: { const { nameSpace, editorState } = action; // 用immutable的setIn只更新指定编辑器的状态 return state.setIn(['editors', nameSpace, 'state'], editorState); // 如果是普通JS对象的写法: // return { // ...state, // editors: { // ...state.editors, // [nameSpace]: { // ...state.editors[nameSpace], // state: editorState // } // } // }; }
二、组件层面防抖,减少高频Action分发
Draft.js的onChange会在每一次输入(包括单个字符)时触发,直接dispatch action会导致Redux和Saga高频工作。在组件里添加防抖,只在用户停止输入一段时间后才更新store:
import { debounce } from 'lodash'; import { useCallback } from 'react'; // 组件内部 const YourEditorComponent = ({ nameSpace, dispatch }) => { // 防抖函数:停止输入500ms后再分发action const debouncedUpdate = useCallback( debounce((editorState) => { dispatch(editorStateChangeAction(editorState, nameSpace)); }, 500), [dispatch, nameSpace] ); const handleEditorChange = (editorState) => { debouncedUpdate(editorState); }; return ( <Editor editorState={yourEditorState} onEditorStateChange={handleEditorChange} // 其他props /> ); };
三、优化Saga逻辑,精准判断内容变化
原来的Saga里遍历数组找编辑器、用===对比内容的方式既低效又可能不准确,改用Draft.js官方提供的isContentEqual判断内容是否真的变化,同时直接通过nameSpace定位目标编辑器:
import { isContentEqual } from 'draft-js'; export function* handleOnEditorStateChange({ editorState, nameSpace }) { // 直接通过nameSpace获取当前编辑器的状态,不用遍历数组 const currentEditor = yield select(state => state.editors[nameSpace]); if (!currentEditor) return; const currentContent = currentEditor.state.getCurrentContent(); const newContent = editorState.getCurrentContent(); // 用官方方法判断内容是否真正变化,避免引用不同但内容相同的误判 const hasNewContent = !isContentEqual(newContent, currentContent); if (hasNewContent) { // 只传递需要更新的nameSpace和editorState,不用传整个数组 yield put(storeEditorStateAction(nameSpace, editorState)); } }
四、用Reselect创建精准Selector,避免不必要的重渲染
每个Editor组件只需要关心自己对应的状态,用Reselect创建缓存的selector,确保只有当自身状态变化时才触发组件更新:
import { createSelector } from 'reselect'; // 基础selector const selectEditors = state => state.editors; // 针对单个编辑器的缓存selector export const selectEditorByNamespace = createSelector( [selectEditors, (_, nameSpace) => nameSpace], (editors, nameSpace) => editors[nameSpace] );
在组件里使用这个selector:
import { useSelector } from 'react-redux'; const YourEditorComponent = ({ nameSpace }) => { const editorData = useSelector(state => selectEditorByNamespace(state, nameSpace)); return ( <Editor editorState={editorData.state} // 其他props /> ); };
五、用React.memo包裹Editor组件,进一步优化重渲染
react-draft-wysiwyg的Editor组件默认没有做memo优化,可以用React.memo包裹它,避免因为父组件无关props变化而触发重渲染:
import React, { memo } from 'react'; import { Editor } from 'react-draft-wysiwyg'; // 创建memo化的Editor组件 const MemoizedEditor = memo(Editor); // 在你的组件里使用MemoizedEditor代替原来的Editor
最后补充说明
这些优化的核心思路是:减少不必要的Redux状态更新次数 + 避免无关组件的重渲染 + 简化状态操作的开销。按照这个思路调整后,高频输入时的性能会有明显提升,[Violation] 'input' handler took <N>ms的警告也会消失。
内容的提问来源于stack exchange,提问作者Space

