问题描述
我正在用 React 制作一个小型 CMS 系统,我有一个表单,用户可以在其中使用 Draft.js 编辑器和其他一些字段。对于这个问题,让我们关注编辑表单。
编辑器的代码如下所示:
import React,{ useRef } from 'react';
import { Formik } from "formik";
import TextInputField from "@/components/TextInputField";
import client from "@/utils/http";
const MyForm = ({title,content}) => {
const editorRef = useRef();
function handleSubmit(values) {
const editorContent = editorRef.current.parse();
client.submit('/api/edit/project',{ editorContent,...values });
}
return (
<Formik onSubmit={formik.handleSubmit} initialValues={{ title }}>
{
(formik) => (
<form onSubmit={formik.handleSubmit}>
<TextInputField label="title" name="title" />
<RichEditor ref={editorRef} content={content} />
</form>
)}
</Formik>);
}
import React,{ useImperativeHandle,useState } from "react";
import {
Editor,EditorState,convertFromHTML,ContentState,convertToRaw,} from "draft-js";
import draftToHtml from "draftjs-to-html";
function createFromContent(htmlContent) {
const blocksFromHtml = convertFromHTML(htmlContent);
const editorState = ContentState.createFromBlockArray(
blocksFromHtml.contentBlocks,blocksFromHtml.entityMap
);
return EditorState.createWithContent(editorState);
}
function formatToHTML(editorState) {
const raw = convertToRaw(editorState.getCurrentContent());
const markup = draftToHtml(raw);
return markup;
}
function RichEditor({ content = null },ref) {
const [editorState,setEditorState] = useState(() =>
content ? createFromContent(content) : EditorState.createEmpty()
);
useImperativeHandle(
ref,() => ({
parse: () => {
return formatToHTML(editorState);
},}),[editorState]
);
return (
<div className="App-Rich-Editor w-full block border border-gray-300 rounded-md mt-4 shadow-sm">
<Editor
placeholder="Enter your content..."
editorState={editorState}
onChange={setEditorState}
/>
</div>
);
}
export default React.forwardRef(RichEditor);
这有效,但它给我带来了以下问题,因此为什么要问社区,因为使用 useImperativeHandle
似乎是一种“黑客”。因为即使是 React 文档也不鼓励使用它。
一如既往,在大多数情况下应该避免使用引用的命令式代码。
因为我想格式化编辑器的内部状态仅一次,所以当我提交表单时,我显示的代码是否合理,即使它“逆势而上”,通过使用命令句柄与父级共享子状态。
这让我想到了问题:
- 在这种情况下是否可以
useImperativeHandle
挂钩以进行“优化”,以便我们仅在需要时获取状态? - 是否有一些更好的方法可以使用“常见”模式(例如“提升状态”、“渲染道具”或其他方式)来实现此实现?
- 我是否忽略了此处的问题,我是否应该硬着头皮将整个编辑器状态与
formik
同步,方法是将其从组件中提起,然后在提交时对其进行格式化?
对我来说,第三个选项似乎打破了关注点的分离,因为它会用状态逻辑污染 Form
上下文,感觉它不属于那里。
解决方法
以我的拙见,提供的解决方案有点过度设计。因此,让我就您提出的问题提供我的想法:
- 我没有看到使用
useImperativeHandle
的优化,因为值同时存储在 ref 和RichEditor
状态 -
formatToHTML
函数似乎是纯函数。那么为什么不导出它并在表单提交之前使用,而不是使用forwardRef
和useImperativeHandle
- 这是我的建议,我认为这正是您在第三个要点中提到的:
import TextInputField from "@/components/TextInputField";
import client from "@/utils/http";
import {
ContentState,convertFromHTML,convertToRaw,Editor,EditorState,} from "draft-js";
import draftToHtml from "draftjs-to-html";
import { Formik } from "formik";
import React,{ useCallback } from "react";
function createFromContent(htmlContent) {
const blocksFromHtml = convertFromHTML(htmlContent);
const editorState = ContentState.createFromBlockArray(
blocksFromHtml.contentBlocks,blocksFromHtml.entityMap
);
return EditorState.createWithContent(editorState);
}
function formatToHTML(editorState) {
const raw = convertToRaw(editorState.getCurrentContent());
const markup = draftToHtml(raw);
return markup;
}
const MyForm = ({ title,content }) => {
const [editorState,setEditorState] = useState(() =>
content ? createFromContent(content) : EditorState.createEmpty()
);
const handleSubmit = useCallback(
(values) => {
const editorContent = formatToHTML(editorState);
client.submit("/api/edit/project",{ editorContent,...values });
},[editorState]
);
return (
<Formik onSubmit={handleSubmit} initialValues={{ title }}>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<TextInputField label="title" name="title" />
<div className="App-Rich-Editor w-full block border border-gray-300 rounded-md mt-4 shadow-sm">
<Editor
placeholder="Enter your content..."
editorState={editorState}
onChange={setEditorState}
/>
</div>
</form>
)}
</Formik>
);
};
export default MyForm;