在 Draft.JS 编辑器和 Formik 中使用 useImperativeHandle 钩子是否合理?
Is using useImperativeHandle hook justified here with Draft.JS editor and Formik?
我正在用 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
似乎是一个“hack”。因为即使是 React 文档也不鼓励使用它。
As always, imperative code using refs should be avoided in most cases.
因为我想格式化编辑器的内部状态只一次,当我提交表单时,我显示的代码是否合理,即使它“逆流而上”,通过使用命令句柄与父级共享子状态。
这让我想到以下问题:
- 在这种情况下
useImperativeHandle
挂钩是否可以,以便“优化”以便我们仅在需要时获取状态?
- 有没有更好的方法来使用“常规”模式实现此实现,例如“提升状态”、“渲染道具”或其他方式?
- 我是否忽略了这里的问题,我是否应该硬着头皮将整个编辑器状态与
formik
同步,将其从组件中提取出来,然后在提交时对其进行格式化?
对我来说,第三个选项似乎打破了关注点分离,因为它会用状态逻辑污染 Form
上下文,感觉它不属于那里。
以我的拙见,提供的解决方案有点过度设计。因此,让我就您提出的问题提供我的看法:
- 我没有看到使用
useImperativeHandle
的优化,因为值同时存储在 ref 和 RichEditor
state 中
formatToHTML
函数似乎是纯函数。那么为什么不导出它并在表单提交之前使用它而不是使用 forwardRef
和 useImperativeHandle
使事情复杂化
- 这是我的建议,我认为这正是您在第 3 个项目符号中提到的:
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;
我正在用 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
似乎是一个“hack”。因为即使是 React 文档也不鼓励使用它。
As always, imperative code using refs should be avoided in most cases.
因为我想格式化编辑器的内部状态只一次,当我提交表单时,我显示的代码是否合理,即使它“逆流而上”,通过使用命令句柄与父级共享子状态。
这让我想到以下问题:
- 在这种情况下
useImperativeHandle
挂钩是否可以,以便“优化”以便我们仅在需要时获取状态? - 有没有更好的方法来使用“常规”模式实现此实现,例如“提升状态”、“渲染道具”或其他方式?
- 我是否忽略了这里的问题,我是否应该硬着头皮将整个编辑器状态与
formik
同步,将其从组件中提取出来,然后在提交时对其进行格式化?
对我来说,第三个选项似乎打破了关注点分离,因为它会用状态逻辑污染 Form
上下文,感觉它不属于那里。
以我的拙见,提供的解决方案有点过度设计。因此,让我就您提出的问题提供我的看法:
- 我没有看到使用
useImperativeHandle
的优化,因为值同时存储在 ref 和RichEditor
state 中
formatToHTML
函数似乎是纯函数。那么为什么不导出它并在表单提交之前使用它而不是使用forwardRef
和useImperativeHandle
使事情复杂化
- 这是我的建议,我认为这正是您在第 3 个项目符号中提到的:
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;