使用react-router检测路由更改

时间:2017-07-28 12:36:52

标签: reactjs react-router react-router-v4 react-router-redux react-router-dom

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

我想做的是这样的事情:

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

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

8 个答案:

答案 0 :(得分:61)

尝试检测路线更改时,您可以使用history.listen()功能。考虑到您正在使用react-router v4,请使用withRouter HOC包装您的组件以访问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')
);

然后在 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);

来自历史docs

  

您可以使用侦听当前位置的更改   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 - 此位置的一些额外状态(不在网址中)(createBrowserHistory支持   createMemoryHistory

     

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

     

该操作是PUSH, REPLACE, or POP之一,具体取决于用户的方式   得到了当前的网址。

当您使用react-router 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>
        )
    }
} 

答案 1 :(得分:38)

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>
  );
};

答案 2 :(得分:11)

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) 
  })
  ...
}

请务必注意,上述所有在 first 客户端路由上发生的火灾以及随后的更改。如果有问题,请使用后面的示例,并在执行任何操作之前检查prevLocation是否存在。

答案 3 :(得分:8)

如果您想全局收听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>

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

答案 4 :(得分:8)

这是一个古老的问题,我不太了解侦听路线变更以推动路线变更的业务需求;似乎是回旋处。

但是,如果您由于在这里想要做的所有事情只是为了针对Google Analytics(分析)/全局站点标签/类似内容在React-Router路由更改中更新class Money { protected amount: number = 0; public equals(other: any): boolean { if (!(other instanceof Money)) return false; // not a Money derivative thus not equal return this.amount === other.amount; // "other" is now considered an instance of Money by the compiler/linter by process of elimination } } class Dollar extends Money { constructor(amount: number){ super(); this.amount = amount; } } 而来到这里,这是一个 hook 您现在可以使用。我根据公认的答案写了它:

'page_path'

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

答案 5 :(得分:1)

反应路由器V5

如果要将pathName作为字符串(“ /”或“用户”),则可以使用以下内容:

  // React Hooks: React Router DOM
  let history = useHistory();
  const location = useLocation();
  const pathName = location.pathname;

答案 6 :(得分:0)

在遇到React单页应用程序中的新屏幕后,我试图将ChromeVox屏幕阅读器聚焦到“屏幕”顶部时遇到了这个问题。基本上是尝试通过跟随指向新的服务器呈现的网页的链接来模拟如果加载此页面时将发生的情况。

此解决方案不需要任何侦听器,它使用withRouter()componentDidUpdate()生命周期方法来触发单击,以便在导航到新的URL路径时将ChromeVox集中在所需的元素上。


实施

我创建了一个“屏幕”组件,该组件包裹在包含所有应用程序屏幕的react-router开关标签周围。

<Screen>
  <Switch>
    ... add <Route> for each screen here...
  </Switch>
</Screen>

Screen.tsx组件

注意:该组件使用React + TypeScript

import React from 'react'
import { RouteComponentProps, withRouter } from 'react-router'

class Screen extends React.Component<RouteComponentProps> {
  public screen = React.createRef<HTMLDivElement>()
  public componentDidUpdate = (prevProps: RouteComponentProps) => {
    if (this.props.location.pathname !== prevProps.location.pathname) {
      // Hack: setTimeout delays click until end of current
      // event loop to ensure new screen has mounted.
      window.setTimeout(() => {
        this.screen.current!.click()
      }, 0)
    }
  }
  public render() {
    return <div ref={this.screen}>{this.props.children}</div>
  }
}

export default withRouter(Screen)

我曾经尝试使用focus()而不是click(),但是单击导致ChromeVox停止读取当前正在读取的内容,并在我告诉它开始的地方重新开始。

高级注释::在此解决方案中,导航<nav>位于屏幕组件内部,并且在<main>内容之后呈现,该导航main使用css order: -1;。所以用伪代码:

<Screen style={{ display: 'flex' }}>
  <main>
  <nav style={{ order: -1 }}>
<Screen>

如果您对此解决方案有任何想法,意见或建议,请添加评论。

答案 7 :(得分:0)

import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Sidebar from './Sidebar';
import Chat from './Chat';

<Router>
    <Sidebar />
        <Switch>
            <Route path="/rooms/:roomId" component={Chat}>
            </Route>
        </Switch>
</Router>

import { useHistory } from 'react-router-dom';
function SidebarChat(props) {
    **const history = useHistory();**
    var openChat = function (id) {
        **//To navigate**
        history.push("/rooms/" + id);
    }
}

**//To Detect the navigation change or param change**
import { useParams } from 'react-router-dom';
function Chat(props) {
    var { roomId } = useParams();
    var roomId = props.match.params.roomId;

    useEffect(() => {
       //Detect the paramter change
    }, [roomId])

    useEffect(() => {
       //Detect the location/url change
    }, [location])
}