#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
не в theEditor
. Следовательно,Editor
не имеет никаких предположений о том, что находится внутриeditorState
, и вместоsetEditorState
этого вы могли бы передать какой-то другой обратный вызов на уровнеMyForm
, который означает, что способeditorState
изменения определен внутриMyForm
. Изменение запускается только вEditor
.4. Понятно, спасибо за доработку и ценю ответ