React TinyMCE配合react-hook-form调用setValue触发无限循环问题求助
问题原因
- TinyMCE的
init配置直接定义在组件的JSX属性中,每次组件重渲染都会生成全新的对象引用,TinyMCE检测到配置变更就会触发编辑器重新初始化。 react-hook-form的setValue方法默认会触发表单的全局重渲染,当前编辑器组件也会同步重渲染。在onEditorChange中直接调用setValue时,会形成「编辑器内容更新→调用setValue→表单重渲染→TinyMCE重新初始化→再次触发onEditorChange」的无限循环,最终导致应用崩溃、控制台报错激增。- 额外维护的
htmlContent本地状态每次更新也会触发组件重渲染,进一步加快了循环触发频率。
解决方案
方案1:基础修复(改动最小)
- 将固定的
init配置移到组件外部,固定引用避免触发TinyMCE重复初始化 - 调用
setValue时添加参数,关闭不必要的重渲染触发 - 可以直接去掉冗余的本地
htmlContent状态,直接同步内容到表单即可
修复后代码示例:
import React from 'react' import { Editor } from '@tinymce/tinymce-react' import { useFormContext } from 'react-hook-form' import { HtmlEditorWrapper } from './HtmlEditor.styles' // 将固定init配置移到组件外部 const TINYMCE_INIT = { height: 200, menubar: false, plugins: [ 'advlist autolink lists link image charmap print preview anchor', 'searchreplace visualblocks code fullscreen', 'insertdatetime media table paste code help wordcount', ], toolbar: 'undo redo | formatselect | ' + 'bold italic backcolor | alignleft aligncenter ' + 'alignright alignjustify | bullist numlist outdent indent | ' + 'removeformat | help', content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }', } type HtmlEditorProps = { name: string } export const HtmlEditorComponent = ({ name }: HtmlEditorProps): JSX.Element => { const { setValue, getValues } = useFormContext() return ( <> <HtmlEditorWrapper> <Editor value={getValues(name) || ''} apiKey={process.env.REACT_APP_TINYMCE_KEY} onEditorChange={(content: string) => { // 调用setValue时添加配置,避免不必要的全局重渲染 setValue(name, content, { shouldDirty: true, shouldTouch: true, shouldValidate: false // 不需要实时校验可以关闭 }) }} init={TINYMCE_INIT} /> </HtmlEditorWrapper> </> ) }
方案2:使用Controller集成(更推荐,符合最佳实践)
用react-hook-form官方提供的Controller组件对接第三方受控组件,内部已经做了重渲染优化,不需要手动调用setValue,更稳定:
import React from 'react' import { Editor } from '@tinymce/tinymce-react' import { useFormContext, Controller } from 'react-hook-form' import { HtmlEditorWrapper } from './HtmlEditor.styles' const TINYMCE_INIT = { height: 200, menubar: false, plugins: [ 'advlist autolink lists link image charmap print preview anchor', 'searchreplace visualblocks code fullscreen', 'insertdatetime media table paste code help wordcount', ], toolbar: 'undo redo | formatselect | ' + 'bold italic backcolor | alignleft aligncenter ' + 'alignright alignjustify | bullist numlist outdent indent | ' + 'removeformat | help', content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }', } type HtmlEditorProps = { name: string } export const HtmlEditorComponent = ({ name }: HtmlEditorProps): JSX.Element => { const { control } = useFormContext() return ( <> <HtmlEditorWrapper> <Controller name={name} control={control} defaultValue="" render={({ field }) => ( <Editor value={field.value} apiKey={process.env.REACT_APP_TINYMCE_KEY} onEditorChange={(content: string) => { field.onChange(content) }} init={TINYMCE_INIT} /> )} /> </HtmlEditorWrapper> </> ) }
方案3:添加防抖(可选)
如果不需要内容实时同步到表单,可以对更新逻辑添加防抖,进一步降低性能消耗:
// 安装lodash后引入,或者自己实现防抖函数 import { debounce } from 'lodash' // 组件内部定义防抖更新函数 const updateFormValue = debounce((content: string) => { setValue(name, content, { shouldDirty: true, shouldTouch: true }) }, 300) // onEditorChange中调用 onEditorChange={(content: string) => { updateFormValue(content) }}
内容的提问来源于stack exchange,提问作者walker1
相关产品推荐
相关产品推荐

