使用 react-router 检测路由变化

IT技术 reactjs react-router react-router-v4 react-router-redux react-router-dom
2021-03-26 02:06:16

我必须根据浏览历史实现一些业务逻辑。

我想做的是这样的:

reactRouter.onUrlChange(url => {
   this.history.push(url);
});

当 URL 更新时,有没有办法从 react-router 接收回调?

6个回答

您可以history.listen()在尝试检测路线变化时使用功能。考虑到您正在使用react-router v4,请使用withRouterHOC包装您的组件以访问historyprops。

history.listen()返回一个unlisten函数。你会用它来unregister听。

您可以配置您的路线,如

索引.js

ReactDOM.render(
      <BrowserRouter>
            <AppContainer>
                   <Route exact path="/" Component={...} />
                   <Route exact path="/Home" Component={...} />
           </AppContainer>
        </BrowserRouter>,
  document.getElementById('root')
);

然后在AppContainer.js

class App extends Component {
  
  componentWillMount() {
    this.unlisten = this.props.history.listen((location, action) => {
      console.log("on route change");
    });
  }
  componentWillUnmount() {
      this.unlisten();
  }
  render() {
     return (
         <div>{this.props.children}</div>
      );
  }
}
export default withRouter(App);

从历史文档

您可以使用以下命令侦听当前位置的更改 history.listen

history.listen((location, action) => {
      console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
  console.log(`The last navigation action was ${action}`)
})

location 对象实现了 window.location 接口的一个子集,包括:

**location.pathname** - The path of the URL
**location.search** - The URL query string
**location.hash** - The URL hash fragment

位置还可能具有以下属性:

location.state - 此位置的一些额外状态,不在 URL 中(在createBrowserHistory和 中 支持createMemoryHistory

location.key- 表示此位置的唯一字符串(在createBrowserHistory和 中支持createMemoryHistory

该操作是一种PUSH, REPLACE, or POP取决于用户如何到达当前 URL 的操作。

当您使用 react-router v3 时,您可以使用上面提到history.listen()fromhistory包,也可以使用browserHistory.listen()

您可以配置和使用您的路线,例如

import {browserHistory} from 'react-router';

class App extends React.Component {

    componentDidMount() {
          this.unlisten = browserHistory.listen( location =>  {
                console.log('route changes');
                
           });
      
    }
    componentWillUnmount() {
        this.unlisten();
     
    }
    render() {
        return (
               <Route path="/" onChange={yourHandler} component={AppContainer}>
                   <IndexRoute component={StaticContainer}  />
                   <Route path="/a" component={ContainerA}  />
                   <Route path="/b" component={ContainerB}  />
            </Route>
        )
    }
} 
@KyleRichardson,你看到了我的完整答案,我也在 v3 中添加了方法。还有一件事,OP评论说他今天使用的是v3,我昨天已经回答了这个问题
2021-05-25 02:06:16
@ShubhamKhatri 是的,但是您的答案读取方式是错误的。他没有使用 v4 ......另外,为什么每次路由发生history.listen()时使用withRouter已经用新道具更新你的组件你会使用您可以对nextProps.location.href === this.props.location.hrefincomponentWillUpdate进行简单的比较,以便在它发生变化时执行您需要执行的任何操作。
2021-05-29 02:06:16
@Aris,你有没有改变尝试一下
2021-06-01 02:06:16
他正在使用 v3,您的答案的第二句话说“考虑到您正在使用react-router v4
2021-06-15 02:06:16
@KyleRichardson 我想你又误解了我,我当然必须学习我的英语。我的意思是,如果您使用的是 react-router v4 并且您使用的是历史对象,那么您需要用withRouter
2021-06-21 02:06:16

React Router 5.1+ 的更新。

import React from 'react';
import { useLocation, Switch } from 'react-router-dom'; 

const App = () => {
  const location = useLocation();

  React.useEffect(() => {
    console.log('Location changed');
  }, [location]);

  return (
    <Switch>
      {/* Routes go here */}
    </Switch>
  );
};

react-router v6

在即将发布的v6 中,这可以通过组合useLocationuseEffect钩子来完成

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

const MyComponent = () => {
  const location = useLocation()

  React.useEffect(() => {
    // runs on location, i.e. route, change
    console.log('handle route change here', location)
  }, [location])
  ...
}

为了方便重用,您可以在自定义useLocationChange钩子中执行此操作

// runs action(location) on location, i.e. route, change
const useLocationChange = (action) => {
  const location = useLocation()
  React.useEffect(() => { action(location) }, [location])
}

const MyComponent1 = () => {
  useLocationChange((location) => { 
    console.log('handle route change here', location) 
  })
  ...
}

const MyComponent2 = () => {
  useLocationChange((location) => { 
    console.log('and also here', location) 
  })
  ...
}

如果你还需要在变化时看到之前的路线,你可以结合一个usePrevious钩子

const usePrevious = (value) => {
  const ref = React.useRef()
  React.useEffect(() => { ref.current = value })

  return ref.current
}

const useLocationChange = (action) => {
  const location = useLocation()
  const prevLocation = usePrevious(location)
  React.useEffect(() => { 
    action(location, prevLocation) 
  }, [location])
}

const MyComponent1 = () => {
  useLocationChange((location, prevLocation) => { 
    console.log('changed from', prevLocation, 'to', location) 
  })
  ...
}

重要的是要注意,上述所有内容都会在安装第一个客户端路由以及后续更改上触发如果这是一个问题,请使用后一个示例并prevLocation在执行任何操作之前检查 a 是否存在。

我有个问题。如果已经渲染了多个组件并且它们都在监视 useLocation,那么它们的所有 useEffects 都会被触发。如何验证此位置对于将显示的特定组件是否正确?
2021-05-30 02:06:16
嘿@Kex - 只是为了澄清location这里是浏览器位置,所以它在每个组件中都是相同的,并且在这个意义上总是正确的。如果您在不同的组件中使用钩子,当位置发生变化时,它们都会收到相同的值。我想他们对这些信息的处理会有所不同,但它始终是一致的。
2021-06-01 02:06:16
如何使用 TS 做同样的 useLocationChange ?并且 react 抱怨 React Hook useEffect 缺少依赖项:'action'。包括它或删除依赖项数组。如果 'action' 更改过于频繁,请找到定义它的父组件并将该定义包装在 useCallback react-hooks/exhaustive-deps 中
2021-06-13 02:06:16
除非我做类似 if (location.pathName === “dashboard/list”) { ..... actions } 的事情。尽管如此,它似乎不是一个非常优雅的组件硬编码路径。
2021-06-18 02:06:16
这就说得通了。只是想知道组件如何知道位置更改是否与执行操作有关。例如,一个组件接收仪表板/列表,但它如何知道它是否与该位置相关联?
2021-06-19 02:06:16

如果要history全局侦听对象,则必须自己创建它并将其传递给Router. 然后你可以用它的listen()方法来听它

// Use Router from react-router, not BrowserRouter.
import { Router } from 'react-router';

// Create history object.
import createHistory from 'history/createBrowserHistory';
const history = createHistory();

// Listen to history changes.
// You can unlisten by calling the constant (`unlisten()`).
const unlisten = history.listen((location, action) => {
  console.log(action, location.pathname, location.state);
});

// Pass history to Router.
<Router history={history}>
   ...
</Router>

如果您将历史对象创建为module,那就更好了,这样您就可以轻松地将它导入到您需要的任何地方(例如 import history from './history';

什么时候必须调用 unlisten()?整个应用什么时候卸载?
2021-06-16 02:06:16

这是一个老问题,我不太了解监听路由更改以推动路由更改的业务需求;似乎迂回。

但是,如果您最终来到这里是因为您想要的只是更新'page_path'Google 分析/全局站点标签/类似内容的 react-router 路由更改,这里有一个您现在可以使用钩子我根据接受的答案写了它:

useTracking.js

import { useEffect } from 'react'
import { useHistory } from 'react-router-dom'

export const useTracking = (trackingId) => {
  const { listen } = useHistory()

  useEffect(() => {
    const unlisten = listen((location) => {
      // if you pasted the google snippet on your index.html
      // you've declared this function in the global
      if (!window.gtag) return

      window.gtag('config', trackingId, { page_path: location.pathname })
    })

    // remember, hooks that add listeners
    // should have cleanup to remove them
    return unlisten
  }, [trackingId, listen])
}

你应该在你的应用程序中使用一次这个钩子,靠近顶部但仍在路由器内。我有一个App.js看起来像这样的:

App.js

import * as React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'

import Home from './Home/Home'
import About from './About/About'
// this is the file above
import { useTracking } from './useTracking'

export const App = () => {
  useTracking('UA-USE-YOURS-HERE')

  return (
    <Switch>
      <Route path="/about">
        <About />
      </Route>
      <Route path="/">
        <Home />
      </Route>
    </Switch>
  )
}

// I find it handy to have a named export of the App
// and then the default export which wraps it with
// all the providers I need.
// Mostly for testing purposes, but in this case,
// it allows us to use the hook above,
// since you may only use it when inside a Router
export default () => (
  <BrowserRouter>
    <App />
  </BrowserRouter>
)
我已经尝试过你的代码,但是当我改变路线时它无法检测到。当我刷新页面时它起作用。但是什么时候更改路线 useTracking() 不会在 app.js 中再次调用,有什么方法可以让我在路线更改时再次调用 useTracking() 吗?
2021-06-18 02:06:16