使用Milkdown编辑器组件时状态更新无法触发重渲染问题
Milkdown编辑器无法加载异步获取内容的问题修复与最佳实践
问题分析
你的代码存在以下几个核心问题导致编辑器无法正常展示异步获取的内容:
- useEditor依赖缺失:
useEditor的第二个参数为空数组,content更新时不会触发编辑器重新初始化,而defaultValueCtx仅在编辑器首次创建时生效,后续content变化无法同步到编辑器。 - JSON序列化冗余处理:如果
content.body本身就是Markdown字符串,JSON.stringify会给内容添加引号并转义特殊字符,导致编辑器无法识别正确格式。 - Loading状态管理错误:异步请求未完成就调用
setLoading(false),页面提前渲染编辑器但此时body仍是空字符串。 - 强制更新逻辑无效:
forceUpdate函数仅定义未调用,无法触发组件重渲染。
修复后的代码
WikiEditorWrapper组件
import React, { useState, useEffect } from 'react'; import EditorButton from '../../components/Button/EditorButton'; import { fetchCall } from '../../utils/testhandler'; import { setCookieTest } from '../../utils/cookieHandler'; import SaveIcon from '@mui/icons-material/Save'; import { MilkdownProvider } from '@milkdown/react'; import '@milkdown/theme-nord/style.css' import WikiEditor from './WikiEditor'; export const WikiEditorWrapper: React.FC = () => { const [loading, setLoading] = useState<boolean>(true); const [body, setBody] = useState<string>(""); const [updates, setUpdates] = useState<string>(""); useEffect(() => { const init = async () => { setCookieTest(); try { const pageData = await fetchPageToEdit(1); setBody(pageData); } catch (err) { console.error('加载页面内容失败:', err); } finally { setLoading(false); } }; init(); }, []); const fetchPageToEdit = async (pageID: number) => { const content = await fetchCall(`pages/${pageID}`); // 仅当content.body不是字符串时才序列化,避免冗余转义 return typeof content.body === 'string' ? content.body : JSON.stringify(content.body); }; if (loading) { return ( <section> <p>Loading...</p> </section> ); }; return ( <MilkdownProvider> <EditorButton pageContent={updates} icon={<SaveIcon />} label="Save" link="/wiki/test" /> <WikiEditor content={body} onUpdate={setUpdates} /> </MilkdownProvider> ); };
WikiEditor组件
import React from 'react'; import { Editor, rootCtx, defaultValueCtx } from '@milkdown/kit/core'; import { nord } from '@milkdown/theme-nord'; import { Milkdown, useEditor } from '@milkdown/react'; import { commonmark } from '@milkdown/kit/preset/commonmark'; import { listener, listenerCtx } from '@milkdown/kit/plugin/listener'; interface EditPageProps { content: string; onUpdate: (content: string) => void; } const WikiEditor: React.FC<EditPageProps> = ({ content, onUpdate }) => { // 将content加入依赖数组,content变化时重新初始化编辑器 useEditor((root) => { return Editor.make() .config(ctx => { ctx.set(rootCtx, root); ctx.set(defaultValueCtx, content); ctx.get(listenerCtx).markdownUpdated((_, markdown) => { onUpdate(markdown); }); }) .config(nord) .use(commonmark) .use(listener); }, [content]); return <Milkdown />; }; export default WikiEditor;
最佳实践
- 异步数据加载规范:将所有异步操作包裹在async函数中,在请求完成(无论成功失败)后再更新loading状态,避免渲染未就绪的内容。
- useEditor依赖管理:将影响编辑器初始化的参数(如content)加入依赖数组,确保参数变化时编辑器能同步更新。
- 避免冗余序列化:提前确认后端返回的数据格式,若已为字符串类型,直接使用即可,无需额外JSON处理。
- 编辑器实例复用(可选):如果不想每次content变化都重新初始化编辑器,可以通过编辑器实例的commands手动更新内容,减少组件销毁重建的开销:
// 在WikiEditor中添加此useEffect替代重新初始化 useEffect(() => { if (editorRef.current && content) { editorRef.current.action(ctx => { ctx.get(editorCtx).commands.markdown(content); }); } }, [content]); - 单向数据流维护:编辑器内容更新时通过onUpdate回调同步到父组件状态,保持状态的单一数据源,避免状态不一致问题。
内容的提问来源于stack exchange,提问作者Tapialj
相关产品推荐
相关产品推荐

