popState监听navigate的跳转不好使?

140 阅读2分钟

问题背景

PM给客户演示产品时,经常需要动态删除页面上的logo等需求,为了方便PM自行搞定,不需要前端开发定制处理,给PM提供了油猴浏览器用户自定义脚本插件。前端开发提供脚本编写文档脚本。

脚本的实现逻辑:监听每次页面加载,或者跳转到一个新的页面时机,处理图片替换的逻辑。

常见的监听URL变化方式

1. hashchange 事件

最简单的方法之一是使用 hashchange 事件监听地址中的哈希部分变化。这适用于URL中带有#号的情况。

window.onhashchange=function(event){ 
    console.log(event); 
    console.log('Hash changed:', window.location.hash);
} 

//或者
window.addEventListener('hashchange', function(event) { 
    console.log(event); 
    console.log('Hash changed:', window.location.hash);
    // 你可以在这里根据新的hash值来加载相应的内容 
});

2. popstate 事件

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的 window 对象上触发。如果当前处于激活状态的历史记录条目是由 history.pushState() 方法创建的或者是由 history.replaceState() 方法修改的,则 popstate 事件的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。

通俗讲解: popState可以监听浏览器除了pushState、replaceState其他三个方法history.back()、history.forward()、history.go().

window.addEventListener('popstate', function(event) { 
    console.log('Location changed:', window.location.pathname); 
    // 根据新的路径加载相应的内容 
});

注意事项

image.png

react-router 中navigate实现

源码

function useNavigateStable(): NavigateFunction {
  let { router } = useDataRouterContext(DataRouterHook.UseNavigateStable);
  let id = useCurrentRouteId(DataRouterStateHook.UseNavigateStable);

  let activeRef = React.useRef(false);
  useIsomorphicLayoutEffect(() => {
    activeRef.current = true;
  });

  let navigate: NavigateFunction = React.useCallback(
    (to: To | number, options: NavigateOptions = {}) => {
      warning(activeRef.current, navigateEffectWarning);

      // Short circuit here since if this happens on first render the navigate
      // is useless because we haven't wired up our router subscriber yet
      if (!activeRef.current) return;

      if (typeof to === "number") {
        router.navigate(to);
      } else {
        router.navigate(to, { fromRouteId: id, ...options });
      }
    },
    [router, id]
  );

  return navigate;
}

上面的代码核心功能是router.navigate(to)这个方法。在 React Router 6 中,router.navigate 的实现主要依赖于 history 包来操作浏览器的历史记录,类似下面:

const router = { 
    navigate(to, options = {}) {
        if (typeof to === 'number') {
            history.go(to); 
        } else { 
            const { replace = false, state } = options;
            if (replace) { 
                history.replace(to, state); 
            } else { 
                history.push(to, state);
            }
        } 
    } 
};

也就是说navigate方法的修改的url地址的改变,其实主要用到history的replace和push两个方法,而这两个方法引起的更改popState是无法监听到的

监听navigate跳转的方式

     const pushState = history.pushState;
     const replaceState = history.replaceState;
     // 重新监听浏览器地址改变,
     history.pushState = function(state, title, url) {
         // 执行相关的操作
          return pushState.apply(history, arguments);
     };
     history.replaceState = function(state, title, url) {
         // 执行相关的操作
          return replaceState.apply(history, arguments);
     };