我有一个基于 React-Router v6 的应用程序。我创建了一个组件,如果安装了该组件,将阻止用户使用“返回”按钮。如果他们决定继续,它会重置应用并将用户重定向到主页。
import { useCallback } from 'react';
import { useBlocker, useLocation, useNavigate } from 'react-router';
const locationProperties = ['pathname', 'search', 'state'];
function isSameLocation(location1, location2) {
return locationProperties.every((property) => location1[property] === location2[property]);
}
export default function NavigationGuard() {
const location = useLocation();
const navigate = useNavigate();
const blocker = useCallback(
({ action, location: nextLocation, retry }) => {
switch (action) {
case 'PUSH':
case 'REPLACE': {
retry();
return;
}
case 'POP': {
if (isSameLocation(nextLocation, location)) {
retry();
return;
}
const answer = confirm('Are you sure you want to leave this page?');
if (answer) {
navigate('/');
}
return;
}
}
},
[location, navigate],
);
useBlocker(blocker);
return null;
}
这很完美,有一个小问题:当用户点击外部链接时,会出现一个要求确认的提示。这是无意的。
我进行了一些调查,似乎每当页面上存在(并处于活动状态)useBlocker 时,history
包都会添加 onBeforeUnload 侦听器,从而导致出现所述不需要的提示。
我能想到的唯一解决方案是侦听即将被点击的链接上的事件,并在实际点击之前禁用 useBlocker,但这似乎太糟糕了。
import { useCallback, useState } from 'react';
import { useBlocker, useLocation, useNavigate } from 'react-router';
import { useEventListener } from '@wojtekmaj/react-hooks';
import { closest } from 'utils/dom';
const locationProperties = ['pathname', 'search', 'state'];
function isSameLocation(location1, location2) {
return locationProperties.every((property) => location1[property] === location2[property]);
}
function isSameOrigin(location1, location2) {
return new URL(location1).origin === new URL(location2).origin;
}
export default function NavigationGuard() {
const location = useLocation();
const navigate = useNavigate();
const [isExternalLinkActive, setIsExternalLinkActive] = useState(false);
function onActionStart(event) {
const link = closest(event.target, 'a');
if (!link || isSameOrigin(document.location, link.href)) {
return;
}
setIsExternalLinkActive(true);
}
function onActionEnd() {
if (!isExternalLinkActive) {
return;
}
setImmediate(() => {
setIsExternalLinkActive(false);
});
}
useEventListener(document, 'pointerdown', onActionStart);
useEventListener(document, 'focusin', onActionStart);
useEventListener(document, 'pointerdown', onActionEnd);
useEventListener(document, 'blur', onActionEnd);
const blocker = useCallback(
({ action, location: nextLocation, retry }) => {
switch (action) {
case 'PUSH':
case 'REPLACE': {
retry();
return;
}
case 'POP': {
if (isSameLocation(nextLocation, location)) {
retry();
return;
}
const answer = confirm('Are you sure you want to leave this page?');
if (answer) {
navigate('/');
}
return;
}
}
},
[location, navigate],
);
useBlocker(blocker, !isExternalLinkActive);
return null;
}
有没有更好的方法来做到这一点?