Оправдано ли здесь использование крючка useImperativeHandle с помощью черновика.Редактор JS и Formik?

#reactjs #react-hooks #formik #draftjs

#reactjs #реагирующие хуки #formik #draftjs

Вопрос:

Я создаю небольшую систему CMS в React, и у меня есть форма, в которой пользователи могут использовать 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 бы контекст логикой состояния, которая просто чувствует, что ей там не место.

Ответ №1:

По моему скромному мнению, предоставленное решение немного перегружено. Итак, позвольте мне просто высказать свои мысли по заданным вами вопросам:

  • Я не вижу оптимизации в использовании 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;
 

Комментарии:

1. Понятно, просто простой последующий вопрос, не считается ли плохой практикой инкапсуляции «утечки» внутреннего состояния из редактора в верхнем контексте, где существует остальная часть состояния? Я прекрасно понимаю, что это называется «поднятием состояния вверх», но это противоречит одному из принципов хорошего дизайна кода и «инкапсуляции». Является ли «повышение состояния» исключением из этого общего правила? И что касается «оптимизации», да, вы помогли мне понять, что я на самом деле ничего не «оптимизировал», я даже добавил больше ненужной сложности.

2. «поднятие состояния вверх» само по себе не является нарушением инкапсуляции. Нарушение инкапсуляции происходит, когда 1 компонент содержит некоторый код, который имеет предположения о 2 компоненте. Например a , код A и B содержит предположение, что A будет иметь a в будущем, когда a изменения в aa коде в B прерываются.

3. Здесь, когда вы проходите editorState , вы также проходите setEditorState , который обрабатывается MyForm не в the Editor . Следовательно, Editor не имеет никаких предположений о том, что находится внутри editorState , и вместо setEditorState этого вы могли бы передать какой-то другой обратный вызов на уровне MyForm , который означает, что способ editorState изменения определен внутри MyForm . Изменение запускается только в Editor .

4. Понятно, спасибо за доработку и ценю ответ