如何将React Draft Wysiwyg编辑器数据从React端提交至Node.js服务端
问题原因
你遇到的[object Object]和内容不生效问题有三个直接诱因:
- Editor组件写法存在语法错误,
onEditorStateChange属性写到了自闭合标签外面,编辑器内容变化时根本不会触发状态更新 - 定义的
descriptionstate从未在编辑器内容更新时赋值,始终是初始化时的空EditorState实例,属于无效状态 - FormData的
append方法接收非字符串/Blob类型的值时,会自动调用值的toString()方法,复杂对象转换后就会输出[object Object],服务端自然无法解析
第一步:修复内容序列化问题,让服务端可正常读取
你不需要单独维护description状态,先修正Editor组件的属性绑定,直接在提交时从最新的editorState里提取内容、转成可序列化的格式即可,两种常用格式按需选择:
// 先修正Editor组件写法,去掉冗余的onEditorStateChange包装函数 return( <Editor editorState={editorState} onEditorStateChange={setEditorState} /> )
方案1:存Draft原生结构化数据(推荐,后续支持回显编辑)
用draft-js内置的convertToRaw方法把EditorState转成可序列化的JSON对象,提交时转成JSON字符串传给服务端即可,后续回显时用convertFromRaw就能直接还原成编辑器可用的状态。
修改提交逻辑:
// 提前导入依赖 import { EditorState, convertToRaw } from 'draft-js'; // 删掉原来无效的const [description, setDescription] = useState(editorState); 代码 const handleSubmit = async (e) => { e.preventDefault(); if(!file) return; // 从当前editorState提取内容转成可序列化结构 const rawContent = convertToRaw(editorState.getCurrentContent()); // 转成字符串供FormData传输 const descriptionStr = JSON.stringify(rawContent); const data = new FormData(); const filename = Date.now() + file.name; data.append("name", filename); data.append("file", file); data.append("username", logUser.userId); data.append("role", logUser.role); data.append("title", title); // 传入转换后的字符串 data.append("description", descriptionStr); data.append("categories", 'Health'); try{ const response = await axiosPrivate.post('/v1/posts', data,{ withCredentials: true, headers:{authorization: `Bearer ${auth.token}`} }) window.location.replace("/post/" + response.data._id); }catch(err){ console.error(err); } }
服务端拿到description字段后,直接调用JSON.parse就能拿到完整结构化内容,可直接存库。
方案2:直接存HTML字符串(适合不需要二次编辑、直接渲染内容的场景)
安装转换工具:npm i draftjs-to-html,提交时直接转成HTML字符串传输:
import draftToHtml from 'draftjs-to-html'; // 提交时转换 const descriptionHtml = draftToHtml(convertToRaw(editorState.getCurrentContent())); data.append("description", descriptionHtml);
服务端拿到的就是标准HTML字符串,可直接存库、前端渲染。
第二步:实现提交时才上传编辑器内图片的逻辑
默认React Draft Wysiwyg会在插入图片时立刻触发上传,你需要先拦截默认上传逻辑,在提交时统一处理图片:
- 给Editor组件配置自定义图片上传回调,插入图片时只生成本地预览地址,不触发服务端请求:
import { Editor } from 'react-draft-wysiwyg'; // 自定义图片插入逻辑,仅本地预览 const uploadImageCallBack = (file) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { resolve({ data: { link: e.target.result } }); }; reader.readAsDataURL(file); }); }; // 给Editor加工具栏配置 return( <Editor editorState={editorState} onEditorStateChange={setEditorState} toolbar={{ image: { uploadCallback: uploadImageCallBack, previewImage: true, uploadEnabled: true, } }} /> )
- 在提交逻辑里,先遍历编辑器内容提取所有图片实体,批量上传到服务端、替换本地地址为正式访问URL后,再提交最终内容:
// 放在handleSubmit里、组装FormData的逻辑之前 const contentState = editorState.getCurrentContent(); let updatedRawContent = convertToRaw(contentState); const entityMap = updatedRawContent.entityMap; const imageUploadTasks = []; // 遍历所有实体筛选图片 Object.keys(entityMap).forEach(key => { const entity = entityMap[key]; if (entity.type === 'IMAGE') { const localSrc = entity.data.link; // 仅本地base64/blob地址需要上传,已存在的服务端地址跳过 if (localSrc.startsWith('data:') || localSrc.startsWith('blob:')) { // 把本地地址转成File对象用于上传 const uploadTask = fetch(localSrc) .then(res => res.blob()) .then(blob => { const imgFile = new File([blob], `editor-img-${Date.now()}.png`, { type: blob.type }); return axiosPrivate.post('/v1/upload-img', imgFile, { headers: { 'Content-Type': 'multipart/form-data', authorization: `Bearer ${auth.token}` } }) }) .then(res => { // 替换为服务端返回的正式图片地址 entityMap[key].data.link = res.data.url; }); imageUploadTasks.push(uploadTask); } } }); // 等所有图片上传完成、地址全部替换完毕,再生成最终内容提交 await Promise.all(imageUploadTasks); const finalDescription = JSON.stringify(updatedRawContent); // 把finalDescription append到FormData中即可
内容的提问来源于stack exchange,提问作者user19263124
相关产品推荐
相关产品推荐

