React排序笔记时TinyMCE编辑器全量重渲染问题求助
问题描述
我正在开发一款工地记录笔记并生成PDF报告的工具,目前笔记的增删改功能均正常,但使用react-sortable-hoc对笔记集合进行拖动排序(灰色按钮为拖拽手柄)时,所有TinyMCE编辑器都会被重新渲染——实际上仅需更新笔记位置,编辑器内容并未改变,却触发了全量初始化。这一操作耗时过长,且若处于页面底部,编辑器的重新初始化会导致页面回滚至顶部。请问是否有办法仅更新编辑器位置而不重新初始化所有编辑器?
关键代码片段
RapportChantier.jsx
export function AddEdit() { const [rapport, setRapport] = useState({ id: null }); const [notes, setNotes] = useState([]); const addNote = () => { notes.push({ uuid: uuidv4(), id: null, content: "Ecrivez votre note ici ...", position: 0, rapport: "/api/rapport_chantiers/" + rapport.id, }); setNotes(notes); }; const removeNote = (id) => { const filteredNotes = notes.filter((note) => { if (note.id !== null) { return note.id !== id; } else { return note.uuid !== id; } }); setNotes(filteredNotes); }; const pushNote = (pushedNote) => { const pushedNoteIndex = notes.findIndex( (note) => note.uuid === pushedNote.uuid ); notes[pushedNoteIndex] = pushedNote; setNotes(notes); }; const onSortEnd = ({ oldIndex, newIndex }) => { const reorderedNotes = arrayMove(notes, oldIndex, newIndex); setNotes(reorderedNotes); }; const SortableNotes = SortableContainer((props) => { return ( <div className={props.className}> {notes.map((note, index) => ( <SortableNote key={note.id} note={note} pushNote={pushNote} removeNote={removeNote} index={index} /> ))} </div> ); }); return ( <div className="bg-white" style={{ padding: "30px" }}> <SortableNotes className="mb-3" axis="y" onSortEnd={onSortEnd} useDragHandle={true} lockAxis="y" /> <button onClick={addNote} className="btn btn-primary btn-sm"> Ajouter une note </button> </div> ); }
Notes.jsx
export const SortableNote = SortableElement(Note); export default function Note({ note: noteProps, removeNote, pushNote }) { const editorRef = useRef(null); const [note, setNote] = useState({ ...noteProps }); const [content, setContent] = useState(noteProps.content); const [timeoutUpdate, setTimeoutUpdate] = useState(null); const [hasChanged, setHasChanged] = useState(0); const updateNote = async () => { if (!note.id) { const { status, response } = await sendJsonData( "/api/notes", { ...note, content }, "post" ); if (status === 201) { setNote(response); } } else { const { status, response } = await sendJsonData( "/api/notes/" + note.id, { ...note, content }, "patch" ); if (status === 200) { setNote(response); } } }; useEffect(() => { if (hasChanged > 0) { clearTimeout(timeoutUpdate); setTimeoutUpdate(setTimeout(updateNote, 1000)); } }, [hasChanged, content]); const DragHandle = SortableHandle(() => ( <button className="btn btn-secondary btn-sm" onClick={(e) => e.preventDefault()} > <span className="icon fa-reorder"></span> </button> )); const handleChanges = (editorContent) => { setContent(editorContent); setHasChanged(hasChanged + 1); }; return ( note && ( <div className="mt-3"> <div className="row"> <div className="col-11"> <Editor tinymceScriptSrc="/libs/tinymce/tinymce.min.js" value={content ?? ""} onEditorChange={handleChanges} onInit={(evt, editor) => (editorRef.current = editor)} init={{ height: 300, menubar: false, plugins: [], toolbar: "undo redo | blocks | bold italic strikethrough underline forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat", content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", }} /> </div> <div className="col-1"> <button className="btn btn-primary btn-sm"> <span className="icon fa-plus"></span> </button> <DragHandle /> <button className="btn btn-danger btn-sm" onClick={(e) => { e.preventDefault(); removeNote(note.id ?? note.uuid); }} > <span className="icon fa-remove"></span> </button> </div> </div> </div> ) ); }
解决方案
问题核心是React在排序时错误地销毁并重建了所有TinyMCE组件,主要原因是key值不稳定、直接修改原数组和组件未做重渲染优化,以下是针对性修复:
1. 修复组件key值,确保唯一且稳定
当前SortableNote的key使用note.id,但新建笔记没有id,会导致key重复或变化,React无法识别组件身份,排序时会销毁重建所有组件。改成用每个笔记都有的uuid:
// RapportChantier.jsx 中SortableNote的key修改 <SortableNote key={note.uuid} note={note} pushNote={pushNote} removeNote={removeNote} index={index} />
2. 使用不可变数组操作,避免直接修改原数组
直接修改notes数组再调用setNotes,React无法检测到数组变化,会触发不必要的全量重渲染,改成创建新数组:
// RapportChantier.jsx 中addNote修改 const addNote = () => { const newNote = { uuid: uuidv4(), id: null, content: "Ecrivez votre note ici ...", position: 0, rapport: "/api/rapport_chantiers/" + rapport.id, }; setNotes([...notes, newNote]); }; // RapportChantier.jsx 中pushNote修改 const pushNote = (pushedNote) => { setNotes(notes.map(note => note.uuid === pushedNote.uuid ? pushedNote : note )); };
3. 用React.memo优化Note组件,减少不必要重渲染
给Note组件包裹React.memo,指定自定义比较函数,只有当关键props变化时才重渲染:
// Notes.jsx 修改 import React, { memo } from 'react'; export const SortableNote = SortableElement(Note); export default memo(function Note({ note: noteProps, removeNote, pushNote }) { // 原有组件逻辑不变 }, (prevProps, nextProps) => { // 仅当笔记的核心属性或回调函数变化时才重渲染 return prevProps.note.uuid === nextProps.note.uuid && prevProps.note.content === nextProps.note.content && prevProps.note.id === nextProps.note.id && prevProps.removeNote === nextProps.removeNote && prevProps.pushNote === nextProps.pushNote; });
4. 优化SortableContainer的props传递
将notes、pushNote、removeNote作为props传给SortableNotes,避免内部直接依赖外部state,让React更精准地判断组件更新:
// RapportChantier.jsx 中SortableNotes修改 const SortableNotes = SortableContainer(({ notes, pushNote, removeNote, className }) => { return ( <div className={className}> {notes.map((note, index) => ( <SortableNote key={note.uuid} note={note} pushNote={pushNote} removeNote={removeNote} index={index} /> ))} </div> ); }); // return中的SortableNotes修改 <SortableNotes className="mb-3" notes={notes} pushNote={pushNote} removeNote={removeNote} axis="y" onSortEnd={onSortEnd} useDragHandle={true} lockAxis="y" />
5. 同步Note组件内部状态与props
在Note组件中添加useEffect监听noteProps变化,确保内部状态与外部props同步:
// Notes.jsx 中添加 useEffect(() => { setNote(noteProps); setContent(noteProps.content); }, [noteProps]);
内容的提问来源于stack exchange,提问作者Jayss230
相关产品推荐
相关产品推荐

