Skip to content

React Router v6 路由变化监听

React Router v6 引入了许多重大变更,其中一个常见问题是如何监听路由变化。与 v5 版本中使用 useHistory 的方式不同,v6 提供了新的 API 来处理路由导航和监听。

问题概述

在 React Router v5 中,开发者可以使用 useHistory hook 来获取历史记录对象,并通过其 listen 方法监听路由变化:

jsx
// React Router v5
const history = useHistory();
history.listen((location, action) => {
  // 路由变化处理逻辑
});

但在 v6 版本中,useHistory 被替换为 useNavigate,而新的 navigate 函数不再提供 listen 方法:

jsx
// React Router v6
const navigate = useNavigate();
navigate.listen(...); // ❌ listen 不是函数

解决方案

方法一:使用 useLocation Hook(推荐)

最简单且最常用的方法是使用 useLocation hook 配合 useEffect

jsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function MyComponent() {
  const location = useLocation();
  
  useEffect(() => {
    // 当 location 变化时执行
    console.log('路由已变更:', location.pathname);
    // 可以在这里添加 analytics 跟踪等逻辑
    ga('send', 'pageview');
  }, [location]); // location 作为依赖项
  
  return <div>当前路径: {location.pathname}</div>;
}

TIP

这种方法适用于大多数常见场景,特别是当您只需要在组件挂载时响应路由变化的情况。

方法二:监听特定导航动作

如果需要区分不同的导航类型(如浏览器前进/后退按钮),可以使用 useNavigationType

jsx
import { useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';

function useBackListener(callback) {
  const location = useLocation();
  const navType = useNavigationType();
  
  useEffect(() => {
    if (navType === 'POP' && location.key !== 'default') {
      // 处理浏览器后退/前进按钮点击
      callback();
    }
  }, [location, navType, callback]);
}

// 使用示例
function MyComponent() {
  useBackListener(() => {
    console.log('用户点击了浏览器后退或前进按钮');
  });
  
  return <div>组件内容</div>;
}

方法三:使用自定义 History 对象(高级)

对于需要更精细控制的场景,可以创建自定义历史记录对象:

jsx
// 首先安装 history 库
// npm install history@5
jsx
// myHistory.js
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
jsx
// CustomRouter.js
import { useState, useLayoutEffect } from 'react';
import { Router } from 'react-router-dom';

const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location
  });

  useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
};

export default CustomRouter;
jsx
// App.js
import CustomRouter from './CustomRouter';
import history from './myHistory';

function App() {
  return (
    <CustomRouter history={history}>
      {/* 您的路由配置 */}
    </CustomRouter>
  );
}
jsx
// 在组件中使用
import { useEffect } from 'react';
import history from './myHistory';

function MyComponent() {
  useEffect(() => {
    const unlisten = history.listen(({ location, action }) => {
      console.log(`路由变化: ${action} -> ${location.pathname}`);
    });
    
    return unlisten; // 清理函数
  }, []);
  
  return <div>组件内容</div>;
}

WARNING

React Router v6.4+ 提供了 unstable_HistoryRouter,但这仍被标记为"不稳定",可能在将来版本中发生变化。

方法四:使用 Data Router 的订阅功能(v6.4+)

如果您使用 React Router v6.4+ 的数据路由器:

jsx
import { createBrowserRouter } from 'react-router-dom';

const router = createBrowserRouter([...]);

// 监听路由状态变化
router.subscribe((state) => {
  console.log('路由状态变更:', state);
});

// 导航到新路径
router.navigate('/path');

// 替换当前历史记录
router.navigate('/path', { replace: true });

最佳实践建议

  1. 优先使用 useLocation:对于大多数用例,useLocation + useEffect 是最简单可靠的解决方案。

  2. 注意组件卸载:如果您的方法涉及在 useEffect 中设置监听器,请确保返回清理函数以避免内存泄漏。

  3. 谨慎使用不稳定 API:避免在生产环境中使用标记为 unstable_ 的 API,因为它们可能在未来的版本中发生变化。

  4. 考虑性能影响:路由变化监听可能会频繁触发,确保您的处理逻辑是高效的。

总结

React Router v6 提供了多种方式来监听路由变化,从简单的 useLocation hook 到高级的自定义历史记录对象。根据您的具体需求选择合适的方法,并遵循 React 的最佳实践来确保代码的可靠性和性能。

INFO

本文基于 React Router v6.8.0,请注意不同版本间可能存在 API 差异,建议查阅官方文档获取最新信息。