# 使用 UseEffect 的错误记录
背景:在 react 中对 monaco-editor 进行初始化,但是会发现有如下两个问题
- monaco-editor 创建的实例对象,被赋值了多次
- monaco-editor 对象被多次挂载销毁,最终要么是 dom 节点上压根就没有 monaco-editor, 要么是有 monaco-editor 相关信息,但是却没有编辑器
# 造成上述现象的 Bad Example
import { FC, useEffect, useRef, useState } from "react"; | |
import styled from "styled-components"; | |
import * as monaco from "monaco-editor"; | |
const EditroContent = styled.div` | |
width: 100%; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
.editor-content-header { | |
} | |
.editor-content-body { | |
width: 100%; | |
height: 100%; | |
background-color: lightblue; | |
} | |
`; | |
const JSContent: FC = () => { | |
const editorEl = useRef<HTMLDivElement>(null); | |
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(); | |
const createEditor =async () => { | |
if (!editorEl.current) return; // 确保 DOM 元素存在 | |
if (editorRef.current) { | |
editorRef.current.dispose(); | |
} | |
if (!editorRef.current) { | |
if (editorEl.current) { | |
editorRef.current = await monaco.editor.create(editorEl.current!, { | |
model: null, | |
minimap: { | |
enabled: false, // 关闭小地图 | |
}, | |
wordWrap: "on", // 代码超出换行 | |
theme: "vs-dark", // 主题 | |
fontSize: 16, | |
fontFamily: "MonoLisa, monospace", | |
contextmenu: false, // 不显示右键菜单 | |
fixedOverflowWidgets: true, // 让语法提示层能溢出容器 | |
readOnly: false, | |
}); | |
} | |
} | |
}; | |
useEffect(() => { | |
console.log(editorEl.current); | |
createEditor(); | |
console.log(editorRef.current); | |
return () => { | |
// 组件卸载时销毁编辑器实例 | |
if (editorRef.current) { | |
editorRef.current.dispose(); | |
} | |
}; | |
}, []); | |
return ( | |
<> | |
<EditroContent> | |
<div className="editor-content-header">JS</div> | |
<div className="editor-content-body" ref={editorEl}></div> | |
</EditroContent> | |
</> | |
); | |
}; | |
export default JSContent; |
# 问题排查
# ques 1
- step1: 本着要把单一职责的原理,我把创建 editor 实例的过程进行了抽离,但是造成了一系列错误。
- step2: 已知 useEffect 会默认执行两次,所以当 console 报错
Element already has context attribute: editor-content-body
时,下意识的会猜测是否是因为 useEffect 默认执行了两次,加上 await 会有一个函数栈的堆积,阻塞进程,进而报错。 - step3: 去除函数的 async await 毕竟 editor 实例挂载只需要在初始挂载时进行一次初始化即可
# ques2
step1:monaco-editor 对象被多次挂载销毁,考虑是否是添加了多次不必要的非空校验,通过 debugger 结果发现问题出现在 createEditor 阶段的判空问题
# 改进
import { FC, useEffect, useRef, useState } from "react"; | |
import styled from "styled-components"; | |
import * as monaco from "monaco-editor"; | |
const EditroContent = styled.div` | |
width: 100%; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
.editor-content-header { | |
} | |
.editor-content-body { | |
width: 100%; | |
height: 100%; | |
background-color: lightblue; | |
} | |
`; | |
const JSContent: FC = () => { | |
const editorEl = useRef<HTMLDivElement>(null); | |
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(); | |
const createEditor = () => { | |
if (!editorEl.current) return; // 确保 DOM 元素存在 | |
if (editorRef.current) { | |
editorRef.current.dispose(); | |
} | |
if (editorEl.current) { | |
editorRef.current = monaco.editor.create(editorEl.current!, { | |
language: "javascript", // Set the language to JavaScript | |
minimap: { enabled: false }, | |
wordWrap: "on", | |
theme: "vs-dark", | |
fontSize: 16, | |
fontFamily: "MonoLisa, monospace", | |
contextmenu: false, | |
fixedOverflowWidgets: true, | |
readOnly: false, | |
}); | |
} | |
}; | |
useEffect(() => { | |
createEditor(); | |
return () => { | |
// 组件卸载时销毁编辑器实例 | |
if (editorRef.current) { | |
editorRef.current.dispose(); | |
} | |
}; | |
}, []); | |
return ( | |
<> | |
<EditroContent> | |
<div className="editor-content-header">JS</div> | |
<div className="editor-content-body" ref={editorEl}></div> | |
</EditroContent> | |
</> | |
); | |
}; | |
export default JSContent; |