需求来源是某页面具有编辑功能,如果用户正在编辑态,且内容还没保存。

如果用户点击了某些地方会导致切换路由,需要弹窗提示是否需要切换。

如果用户想要刷新或关闭浏览器窗口需要提示用户,是否刷新 / 关闭此网站?

基于这个需求想到了四种方法,记录下来方便自己下次复制粘贴。

# 1. componentWillUnmount

首先这个钩子函数是在组件卸载前调用的一个函数,它并不能阻止当前组件的卸载。所以不要想方设法在这里做提示,因为即便提示了,组件还是会卸载,文章还是会消失。

# 2. 路由守卫 - <Prompt/>

为了实现第一个功能,需要一个跳转路由之前进行的判断。在 react-router-dom 4.0 之后取消了先前的路由守卫(其实我没研究过之前版本的,这个描述摘自网络)。在 react-router-dom 4.0 之后,实现这个功能可以依靠 <Prompt/> 组件。文档链接↗

把这个组件添加到你的文章编辑页组件的任意部分

import {Prompt} from 'react-router-dom';
const Editor=()=>{
    return (
        <div>
          <Prompt
            when={true}
            message={location => '文章要保存吼,确定离开吗?'}
          />
        </div>
    )
}

这里有一点需要注意,使用 <Prompt/> 时,你的路由跳转必须通过 <Link/> 实现,而不能依靠 <a/> 原生标签。**
** 点击取消时就会留在当前页面。至此已经实现了路由跳转时提醒用户进行保存的功能。

# 3.history.block
const history = useHistory();
    const releaseRef = useRef(() => {});
    const confirmRef = useRef<{
        destroy(): void;
    } | null>();
    let ref = useRef<any>(show);
    ref.current = show;
    // 切换页面拦截提醒
    useEffect(() => {
        let releaseBlock = history.block((location, action) => {
            if (ref.current) {
                confirmRef.current?.destroy();
                confirmRef.current = Modal.confirm({
                    title: '是否离开当前页面?',
                    content: '当前正在编辑的内容将不会保存',
                    onOk: () => {
                        releaseBlock();
                        if (action === 'PUSH') {
                            history.push(location);
                        } else if (action === 'POP') {
                            history.goBack();
                        } else {
                            history.replace(location);
                        }
                    },
                    onCancel: () => {
                        confirmRef.current?.destroy();
                        return false;
                    },
                });
                return false;
            }
        });
        return () => {};
    }, []);
# 4. 窗口关闭事件 - beforeunload

实现第二个功能需要依靠对窗口的监听。React 应用中对于窗口事件的应用远没有 DOM 事件频繁,所以好久没碰到还是有点手生的。最关键的就是,应该在何时进行监听?

应该在组件挂载时监听事件,组件卸载时移除事件监听。因为我已经开始全面采用 hooks 新特性了,所以这里使用到 useEffect

// 关闭页面拦截
    useEffect(() => {
        if (show) {
            const handleBeforeUnload = (e: any) => {
                e.preventDefault();
                // // 这里没用,浏览器弹窗提示不能自定义
                e.returnValue = '你确定离开此页面吗';
                return e.returnValue;
            };
            window.addEventListener('beforeunload', handleBeforeUnload);
            return () => {
                window.removeEventListener('beforeunload', handleBeforeUnload);
            };
        }
    }, [show]);

这里有几个需要注意的地方:

  1. useEffect 第二个参数为空数组,表示只调用了 componentDidMountcomponentWillUnmount 两个钩子
  2. 事件监听和移除的第二个参数为同一个事件处理函数
  3. beforeunload 事件中的 confirmpromptalert 会被忽略。取而代之的是一个浏览器内置的对话框。(参考:MDN|beforeunload
  4. 必须要有 returnValue 且为非空字符串,但是在某些浏览器中这个值并不会作为弹窗信息