React使用TinyMCE时,能否不使用useEffect处理页面卸载未保存警告?
问题解答
你的当前写法存在隐藏问题,必须用useEffect来实现才符合React的正确开发模式,原因如下:
- 直接在组件顶层赋值
window.onbeforeunload,会在组件每次重新渲染时都覆盖这个全局事件处理函数。比如你的组件因为其他状态/prop变化重新渲染时,都会创建一个新的事件处理函数并覆盖旧的,这会导致不必要的重复赋值。 - 更严重的是闭包陷阱:你当前的事件处理函数捕获的是组件首次渲染时的
editorWasModfied值(初始false)。哪怕之后通过setEditorWasModified把状态改成true,这个事件处理函数里的editorWasModfied依然是false,因为它属于第一次渲染时的闭包。这会导致用户修改内容后,页面卸载时不会弹出警告——只是你目前没遇到这个问题,因为组件还没因为其他原因重新渲染过。
正确实现方式
方式一:通过useEffect绑定,依赖数组包含状态
这种方式最直观,每次editorWasModified变化时,更新事件处理函数:
const [editorWasModified, setEditorWasModified] = useState(false); const editorRef = useRef<TinyMCEEditor | null>(null); useEffect(() => { const handleBeforeUnload = (e) => { if (editorWasModified) { e.preventDefault(); e.returnValue = "你有未保存的更改,确定要离开吗?"; return "你有未保存的更改,确定要离开吗?"; } }; window.addEventListener('beforeunload', handleBeforeUnload); // 组件卸载时清理事件监听 return () => { window.removeEventListener('beforeunload', handleBeforeUnload); }; }, [editorWasModified]); // 依赖状态,状态变化时更新监听
注:现代浏览器对
beforeunload的提示文案有统一限制,自定义文案可能不会显示,所以只需要返回非空值即可,但按规范还是要设置returnValue。
方式二:用Ref同步状态,只绑定一次事件
如果不想每次状态变化都更新事件监听,可以用ref保存最新的状态值,这样事件处理函数只需要绑定一次:
const [editorWasModified, setEditorWasModified] = useState(false); const editorRef = useRef<TinyMCEEditor | null>(null); const modifiedRef = useRef(false); // 同步状态到ref useEffect(() => { modifiedRef.current = editorWasModified; }, [editorWasModified]); useEffect(() => { const handleBeforeUnload = (e) => { if (modifiedRef.current) { e.preventDefault(); e.returnValue = "你有未保存的更改,确定要离开吗?"; return "你有未保存的更改,确定要离开吗?"; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => { window.removeEventListener('beforeunload', handleBeforeUnload); }; }, []); // 空依赖,只执行一次
为什么不能直接在组件顶层写?
React组件每次渲染都会执行顶层的代码,所以你每次渲染都会给window.onbeforeunload赋值新的函数,而且这些函数会捕获各自渲染时的状态值,导致状态更新后,事件函数拿不到最新值。而useEffect是React专门用来处理副作用(比如绑定全局事件)的钩子,能帮我们在正确的时机执行、更新和清理副作用逻辑,符合React的生命周期规则。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

