React路由跳转拦截,react-router-domV5和V6两个版本实现

一、如果使用react-router-domV5版本时,可以使用Prompt组件实现路由跳转拦截。

1、Prompt 是 React Router 提供的一个组件,用于在导航发生之前显示一个提示,以允许或阻止用户进行导航。主要用于提示用户在离开当前页面时是否保存未提交的表单或其他重要信息。以下是 Prompt 的主要属性和功能详细介绍:

  • when (boolean): 表示是否显示提示。当 when 的值为 true 时,Prompt 将生效,显示提示信息;当 when 的值为 false 时,Prompt 将被忽略,导航不会受到阻止。
  • message string | ((location, action) => string | boolean): 提示信息,可以是字符串或一个返回字符串或布尔值的函数。当用户尝试离开当前页面时,Prompt 将根据 message 显示相应的提示信息。如果是函数,该函数接收一个 location 对象参数,可以根据当前导航的位置动态生成提示信息。
    • 如果 message 返回 false,则表示允许正常进行导航,用户将不会看到任何提示,可以自由离开当前页面。
    • 如果 message 返回 true,则表示拦截导航,用户将看到提示信息,并有选择是否继续离开当前页面。在这种情况下,导航将被阻止,直到用户作出决定。

基于以上Prompt组件功能的了解,实现一个Table页面数据发生变化时,如果调转路由,提示用户是否要保存,还是取消的功能。拦截提示的效果如下:

React路由跳转拦截,react-router-domV5和V6两个版本实现_第1张图片

  • 取消:停留在当前页面,不进行路由跳转
  • 不保存:继续调转路由,不保存当前的数据
  • 保存:停留在当前页面,保存当前页面的数据

好的,废话不多说,直接上代码:

// 路由拦截控制
const [jump,setJump] = useState(true);

 {/* 路由守卫,监听页面内容是否发生变化,如果产生变化,未保存前进行路由跳转需要拦截 */}
  {
    localtionRef.current = location;
    // 数据发生变化
    if (handleDataChange()) {
        setOpen(true);
        return false;
     }
     return true;
 }} 
/>


{
    // 拦截的Modal弹框
    open && 取消,
            ,
            ,
        ]}
        closable
        destroyOnClose
    />
}

// 取消,不跳转,继续留在当前页面
const handleOnCancle = async () => {
	// 关闭Modal弹框
	setOpen(false);
	// TODO 下面可以执行该页面的一些初始化的操作
}

// 不保存数据,跳转页面
const handleOnNotSave = async () => {
	try {
		// 关闭弹框
		setOpen(false);
		// 关闭路由守卫的拦截
		setJump(false);
		setTimeout(() => {
			// 继续跳转页面
			if (localtionRef.current) {
				history.push({ ...localtionRef.current });
				if (localtionRef.current?.state?.menuId) {
				localtionRef.current = null;
				// TODO 此处可以执行调转页面的一些需要初始化的操作
			}
		}, 1000);
	} catch (err) {
		console.error('err--:', err);
	} 
}

// 停留在该页面,并执行保存操作
const handleOnSave = async () => {
	try {
		handleOnCancle();
		// TODO 下面是执行该页面的保存逻辑
	} catch (err) {
		console.error('err--', err);
	}
};

二、如果使用react-router-domV6版本时,由于移出了对Prompt组件的支持,所以我们不能直接再使用Prompt组件进行路由拦截,但是官方提供了一个钩子函数unstable_usePrompt()可以进行路由拦截,缺点就是只支持浏览器自带的弹框提示。

unstable_usePrompt源码如下:

/**
 * Wrapper around useBlocker to show a window.confirm prompt to users instead
 * of building a custom UI with useBlocker.
 *
 * Warning: This has *a lot of rough edges* and behaves very differently (and
 * very incorrectly in some cases) across browsers if user click addition
 * back/forward navigations while the confirm is open.  Use at your own risk.
 */
function usePrompt({
  when,
  message,
}: {
  when: boolean | BlockerFunction;
  message: string;
}) {
  let blocker = useBlocker(when);

  React.useEffect(() => {
    if (blocker.state === "blocked") {
      let proceed = window.confirm(message);
      if (proceed) {
        // This timeout is needed to avoid a weird "race" on POP navigations
        // between the `window.history` revert navigation and the result of
        // `window.confirm`
        setTimeout(blocker.proceed, 0);
      } else {
        blocker.reset();
      }
    }
  }, [blocker, message]);

  React.useEffect(() => {
    if (blocker.state === "blocked" && !when) {
      blocker.reset();
    }
  }, [blocker, when]);
}

export { usePrompt as unstable_usePrompt };

这个很明显不符合我之前的要求,将组件react-router-dom从V5升级到V6后,我还希望支持之前的自定义的路由拦截弹框提示,因此不能直接使用该钩子函数,需要我们稍微改造一下使用:

改造代码如下:

export default function useCustomPrompt({
    when,
    message,
}: {
    when: boolean;
    message: string | (() => boolean);
}) {
	// 记录要跳转的路由信息
    const locationRef = useRef(null);
    
	let blocker = useBlocker(when);
	
    const navigate = useNavigate();
    
    React.useEffect(() => {
        try {
            if (blocker.state === "blocked") {
                if (blocker?.location) {
                    // 将有效的信息存储下来
                    locationRef.current = blocker.location;
                }
                if (typeof message === 'function') {
                    message() ? setTimeout(()=>{
                        try {
                            blocker.proceed && blocker.proceed();
                        } catch (err) {
                            console.error('err--',err);
                        }
                    }, 0) : blocker.reset();
                }
                else {
                    let proceed = window.confirm(message);
                    if (proceed) {
                        // This timeout is needed to avoid a weird "race" on POP navigations
                        // between the `window.history` revert navigation and the result of
                        // `window.confirm`
                        setTimeout(()=>{
                        try {
                            blocker.proceed && blocker.proceed();
                        } catch (err) {
                            console.error('err--',err);
                        }
                    }, 0);
                    } else {
                        blocker.reset();
                    }
                }
            } else if (blocker.state === "unblocked" && !when) { // 继续跳转之前要跳转的页面
                // 执行之前存储的路径调转
                newLocationRef.current && navigate(newLocationRef.current.pathname, newLocationRef.current.state);
                // TODO 此处可以执行跳转页面之后的一些初始操作
            }
        } catch (err) {
            console.error('拦截异常:', err);
        }

    }, [blocker, message]);

    React.useEffect(() => {
        try {
            if (blocker.state === "blocked" && !when) {
                blocker.reset && blocker.reset();
            }
        } catch (err) {
            console.error('拦截异常:', err);
        }

    }, [blocker, when]);
}

然后在需要进行路由拦截的页面使用该钩子函数:

// 路由拦截控制
const [jump,setJump] = useState(true);

// 拦截提示信息弹框控制
const [open,setOpen] = useState(false);

// 路由跳转拦截
usePrompt({
	when: jump,
	message: () => {
		// 数据发生变化
		if (handleDataChange()) {
			setOpen(true);
			return false;
		}
		return true;
	}
});

{
	// 拦截的Modal弹框
	open && 取消,
			,
			,
		]}
		closable
		destroyOnClose
	/>
}

// 取消,不跳转,继续留在当前页面
const handleOnCancle = async () => {
	// 关闭Modal弹框
	setOpen(false);
	// TODO 下面可以执行该页面的一些初始化的操作
}

// 不保存数据,跳转页面
const handleOnNotSave = async () => {
	try {
		// 关闭弹框
		setOpen(false);
		// 关闭路由守卫的拦截
		setJump(false);
	} catch (err) {
		console.error('err--:', err);
	} 
}

// 停留在该页面,并执行保存操作
const handleOnSave = async () => {
	try {
		handleOnCancle();
		// TODO 下面是执行该页面的保存逻辑
	} catch (err) {
		console.error('err--', err);
	}
};

你可能感兴趣的:(react.js,javascript,前端)