背景
富文本编辑器:react-quill
当服务端的方法上传图片时,导致输入问题
打字时,导致打一个英文,失焦,而打中文字时,则导致只能打出拼音的第一个字母,中文出不来
解释:用服务端上传图片而不是默认的base64,需要用handlers
现象
1.如果有handlers,且onChange里面用了setState或者dispatch,会导致打字有问题
2.如果handlers去掉,打字没问题
3.handers绑定的image函数为空函数,打字还是有问题
4.有handlers,但是没有重新触发渲染(setState、dispatch),打字没问题
问题代码
Class写法
js
<ReactQuill
ref={ref => {this.state.myRef = ref}}
style={{height:'120px'}}
key='detail'
theme="snow"
modules={
{
toolbar: {
container: this.state.container,
handlers: {
image: () => this.imageHandler()
}
},
clipboard: {
// toggle to add extra line breaks when pasting HTML
matchVisual: false,
}
}
}
value={this.props.default || ''}
onChange={this.handleChange}
/>
handleChange里面如果用setState或者dispatch(设置store) Hook写法
js
function 业务组件(){
const imageHandler = () => {
//服务端上传图片
}
return (
<ReactQuill
ref={quillRef}
value={tourBasicObj.detailIntroductionContent || ''}
placeholder="请输入介绍内容"
onChange={value => handleDetailIntroductionContentChange(value)}
style={{ width: 385 }}
modules={{
toolbar: {
container,
handlers: {
image: imageHandler,
},
},
}
}
/>
)
}
初步猜测
是因为重新render导致quill库认为重新绑定hander,进而重新初始化,因此失焦
寻找根因
源代码结构
1.业务代码使用react-quill
2.react-quill库使用quill,作用是把quill适配成react可以用的库
3.quill才是富文本编辑器的库,富文本编辑功能都在此
第一步
排查为什么有了handlers,且onChange里面有了setState,即使handlers挂载空函数,也会导致失焦
根据ai提示与react-quill源码阅读,猜测可能是由于有了handlers,导致modules改变,进而触发某种重载
为什么onChange里面有了setState,就会有问题
这是由于setState会触发render,重新加载组件
react-quill源码截图
因此尝试调试排查,是因为重新渲染的时候,由于有了handlers,所以导致modules不同吗
本地调试代码模拟如下
父组件

子组件

打一个字,触发setState,导致重新渲染(模拟打字场景)
打印结果
由此结果证明
有了handlers,会导致modules发生变化
第二步
由于有了handlers改变,导致modules发生改变,那么我们需要在源码中寻找答案,由于modules改变,是触发了handers重新挂在或者整个编辑器重新加载
通过在源码里面加console的方法,调试得出结论
由于modules改变,导致触发了unhookEdtior和createEditor,因此导致失焦
调试结果
没有handlers

有handlers

对比得出关键代码,有handlers会触发unhookEditor和createEditor
源码中关键代码截图与解释
1.在生命周期钩子componentWillReceiveProps里面会调用shouldComponentRegenerate,会判断是否为true
2.由于有了handlers,会导致modules不同,因此为true


3.由于为true,因此调用regenerate方法

4.generation方法导致generation状态更新

5.在生命周期钩子componentWillUpdate会判断,generation是否一致,由于状态已经更新,因此不一致,所以调用componentWillUnmount

在componentWillUnmount会触发unhookEdit,导致富文本编辑器失去监听

于是有了答案,由于hanlder,导致modules更新,进而触发unhookEditor和createEditor也就是编辑器重载,因此失焦,编辑器渲染之后是不会主动聚焦的,所以也就解释了为什么输入一个中文,就变成拼音且失焦,输了一个英文字母,就失焦
第三步
探寻为什么有了handlers,会导致modules更新
通过再次复习react,发现答案
如果是函数式写法,发现只要setState,就会触发render,render的时候就会触发所有方法的重载,如果不想要方法重载,就必须用特殊方法,例如把方法放到函数组件外等
如果是Class写法,由于绑定的函数需要用到this或者this.setState,一般来说必须用bind(this)或者用箭头函数,但是如果用了箭头函数,render的时候,就会导致加载了新的方法
结论
根因:由于打字后,onChange里面有setState,触发重新render,导致imageHandler重新定义,最终传给react-quill组件的时候,导致modules发生了变化,最后触发了取消监听editor,再监听editor,进而导致不聚焦(取消监听并重新注册的这个间隙,浏览器会认为编辑区失去了活动状态(尤其是 IME 输入法正在 composition 中时)------这就是失焦发生的地方)
解决方案
Hook函数式写法
1. useMemo
由于useMemo对整个modules做了缓存,保证modules不会发生改变,因此不会触发editor的卸载与重载,进而保持打字正常

2.useRef

3.useCallback
单独对image做useCallback也可以
imageHandlerCb = useCallback(imageHandler,[]);
总而言之,是因为handlers导致modules改变,因此只要让handlers不改变或者modules不改变即可
Class写法

由于modules用的是this.modules,而this.modules只会在初始化执行一次,因此不会改变