使用react-router检测路由更改

Ari*_*ris 49 reactjs react-router react-router-redux react-router-v4 react-router-dom

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

我想做的是这样的:

reactRouter.onUrlChange(url => {
   this.history.push(url);
});
Run Code Online (Sandbox Code Playgroud)

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

ono*_*ndi 75

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>
  );
};
Run Code Online (Sandbox Code Playgroud)


Shu*_*tri 74

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

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

您可以配置您的路线

index.js

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

然后在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);
Run Code Online (Sandbox Code Playgroud)

从历史文档:

您可以使用以下方法侦听当前位置的更改 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}`)
})
Run Code Online (Sandbox Code Playgroud)

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

**location.pathname** - The path of the URL
**location.search** - The URL query string
**location.hash** - The URL hash fragment
Run Code Online (Sandbox Code Playgroud)

地点也可能具有以下属性:

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

location.key- 表示此位置的唯一字符串(支持createBrowserHistorycreateMemoryHistory)

该操作PUSH, REPLACE, or POP取决于用户如何访问当前URL.

当您使用反应路由器V3,你可以利用history.listen()history包如上所述,或者您也可以使用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>
        )
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • @KyleRichardson我想你又误解了我,我当然必须努力提高我的英语水平。我的意思是,如果您使用的是react-router v4并且您正在使用历史对象,那么您需要用“withRouter”包装您的组件 (2认同)

dav*_*wil 35

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])
  ...
}
Run Code Online (Sandbox Code Playgroud)

为了方便重用,您可以在自定义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) 
  })
  ...
}
Run Code Online (Sandbox Code Playgroud)

如果你还需要在变化时看到之前的路线,你可以结合一个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) 
  })
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • 如何使用 TS 执行相同的 useLocationChange 操作?而且 React 还抱怨 React Hook useEffect 缺少依赖项:“action”。包含它或删除依赖项数组。如果“action”更改得太频繁,请找到定义它的父组件并将该定义包装在 useCallback react-hooks/exhaustive-deps 中 (3认同)

Fab*_*ltz 11

如果你想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>
Run Code Online (Sandbox Code Playgroud)

如果您将历史对象创建为模块,则更好,因此您可以轻松地将其导入到您可能需要的任何位置(例如, import history from './history';

  • 什么时候必须调用 unlisten()?当整个应用程序卸载时? (4认同)

Joh*_*pis 11

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

但是,如果您最终来到这里是因为您想要的只是更新'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])
}
Run Code Online (Sandbox Code Playgroud)

你应该在你的应用程序中使用一次这个钩子,靠近顶部但仍在路由器内。我有一个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>
)
Run Code Online (Sandbox Code Playgroud)