在 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.

因为我想格式化编辑器的内部状态只一次,当我提交表单时,我显示的代码是否合理,即使它“逆流而上”,通过使用命令句柄与父级共享子状态。

这让我想到以下问题:

对我来说,第三个选项似乎打破了关注点分离,因为它会用状态逻辑污染 Form 上下文,感觉它不属于那里。

以我的拙见,提供的解决方案有点过度设计。因此,让我就您提出的问题提供我的看法:

  • 我没有看到使用 useImperativeHandle 的优化,因为值同时存储在 ref 和 RichEditor state
  • formatToHTML函数似乎是纯函数。那么为什么不导出它并在表单提交之前使用它而不是使用 forwardRefuseImperativeHandle
  • 使事情复杂化
  • 这是我的建议,我认为这正是您在第 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;