如何将Form组件中的textarea绑定到Tiptap编辑器?
问题:如何在Tiptap中复用表单的textarea元素而非使用content属性?
我是Tiptap的新手,希望使用FormComponent组件中的textarea元素,而非Tiptap编辑器配置项里的content属性,请问这是否可行?
当前尝试的代码如下:
TiptapEditor组件代码
const TiptapEditor = () => { const editor = useEditor({ element: document.getElementById('description'), // 此方式无效 extensions: [StarterKit], content: 'Hello World! 🌎️', // 我不想使用这个 }) return <EditorContent editor={editor} /> } export default TiptapEditor
包含textarea的FormComponent组件代码
const FormComponent = () => { const { register, handleSubmit, watch, formState: { errors, isSubmitting, isSubmitted, isDirty, isValid }, } = useForm<ExampleType>({ mode: 'onChange', resolver: zodResolver(ExampleSchema), defaultValues: { description: '', }, }) return ( <form onSubmit={handleSubmit(submit)} noValidate> <TiptapEditor /> // 目前无法正常工作 <textarea id="description" // 我想使用这个textarea以避免重复样式 placeholder={DESCRIPTION_PLACEHOLDER} {...register('description')} /> <button></button> </form> ) }
我找不到相关示例或文档,请问有什么解决思路吗?
解决思路
可行,但不能直接用document.getElementById在组件初始化阶段获取DOM元素(此时textarea还未渲染完成),可以通过以下步骤实现:
- 通过props传递textarea的ref:在FormComponent中创建textarea的ref,传递给TiptapEditor组件,避免直接操作DOM。
- 同步Tiptap内容与表单字段:利用react-hook-form的
watch监听字段变化,同步到Tiptap编辑器;同时监听Tiptap的内容变化,更新表单字段值。 - 复用原生textarea样式:读取原生textarea的样式,应用到Tiptap编辑器容器,无需重复编写样式代码。
修改后的代码示例
FormComponent组件
import { useRef } from 'react'; import { useForm } from 'react-hook-form'; import TiptapEditor from './TiptapEditor'; const FormComponent = () => { const { register, handleSubmit, watch, setValue, formState: { errors, isSubmitting, isSubmitted, isDirty, isValid }, } = useForm<ExampleType>({ mode: 'onChange', resolver: zodResolver(ExampleSchema), defaultValues: { description: '', }, }); const descriptionRef = useRef(null); const descriptionValue = watch('description'); return ( <form onSubmit={handleSubmit(submit)} noValidate> <TiptapEditor textareaRef={descriptionRef} initialContent={descriptionValue} onContentChange={(content) => setValue('description', content)} /> <textarea ref={descriptionRef} id="description" placeholder={DESCRIPTION_PLACEHOLDER} style={{ display: 'none' }} // 隐藏原生textarea,保留字段注册状态 {...register('description')} /> <button type="submit">提交</button> </form> ) }
TiptapEditor组件
import { useEffect, useRef } from 'react'; import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; const TiptapEditor = ({ textareaRef, initialContent, onContentChange }) => { const editorRef = useRef(null); const editor = useEditor({ extensions: [StarterKit], content: initialContent, onUpdate: ({ editor }) => { onContentChange(editor.getHTML()); // 编辑器内容变化时同步到表单字段 }, onCreate: ({ editor }) => { editorRef.current = editor; // 复制原生textarea的样式到编辑器容器 if (textareaRef.current) { const editorContainer = editor.view.dom.parentNode; const textareaStyles = window.getComputedStyle(textareaRef.current); // 按需复制样式,比如字体、内边距、边框等 editorContainer.style.fontSize = textareaStyles.fontSize; editorContainer.style.fontFamily = textareaStyles.fontFamily; editorContainer.style.padding = textareaStyles.padding; editorContainer.style.border = textareaStyles.border; editorContainer.style.borderRadius = textareaStyles.borderRadius; } }, }); // 监听表单字段变化,同步到编辑器 useEffect(() => { if (editorRef.current && initialContent !== editorRef.current.getHTML()) { editorRef.current.commands.setContent(initialContent); } }, [initialContent]); return <EditorContent editor={editor} /> } export default TiptapEditor;
关键说明
- 安全获取DOM元素:通过React的ref传递获取textarea,避免组件初始化阶段DOM未渲染完成的问题。
- 双向内容同步:实现编辑器与表单字段的实时同步,保证表单数据的一致性。
- 样式复用:通过
getComputedStyle读取原生textarea的样式,直接应用到Tiptap编辑器,避免重复编写样式代码。 - 隐藏原生textarea:隐藏后仍保留其在表单中的注册状态,不影响表单提交逻辑。
内容的提问来源于stack exchange,提问作者JeffMinsungKim
相关产品推荐
相关产品推荐

