用反应路由器检测路由变化

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

我想做的是这样的:

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

当 URL 被更新时,是否有任何方法接收来自反应路由器的回调?

255908 次浏览

您可以利用 history.listen()函数时,试图检测路由的变化。考虑到您正在使用 react-router v4,用 withRouter HOC 包装您的组件以获得对 history道具的访问。

history.listen()返回一个 unlisten函数。

您可以配置您的路由,如

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

来自历史的 医生:

您可以使用以下命令侦听对当前位置的更改 返回文章页面

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

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

State -这个位置的一些额外状态不在 URL 中(在 createBrowserHistorycreateMemoryHistory)

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

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

当您使用反应路由器 v3时,您可以使用上述 history包中的 history.listen(),或者您也可以使用 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>
)
}
}

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

如果将 history 对象作为模块创建,那么效果会更好,这样您就可以轻松地将它导入到任何您可能需要的地方(例如 import history from './history';)

当我在一个 React 单页应用程序中导航到一个新屏幕后,试图将 ChromeVox 屏幕阅读器聚焦到“屏幕”的顶部时,我遇到了这个问题。基本上是试图模拟如果这个页面通过一个链接加载到一个新的服务器渲染的网页会发生什么。

这个解决方案不需要任何侦听器,它使用 withRouter()componentDidUpdate()生命周期方法来触发一次点击,以便在导航到一个新的 url 路径时将 ChromeVox 聚焦到所需的元素上。


实施

我创建了一个“ Screen”组件,它包裹在包含所有应用程序屏幕的反应路由器开关标签周围。

<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 停止读取它当前正在读取的内容,并从我告诉它开始的地方重新开始。

高级说明: 在这个解决方案中,在 Screen 组件内部和在 <main>内容之后渲染的导航 <nav>使用 css order: -1;直观地定位在 main之上。所以在伪代码中:

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

如果您对此解决方案有任何想法、评论或提示,请添加评论。

这是一个古老的问题,我不太理解为了推动路线更改而听取路线更改的业务需求; 似乎是迂回的。

但是,如果你最终在这里,因为你想要的是更新 '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

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

React 路由器5.1 + 的更新。

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


function SomeComponent() {
const location = useLocation();


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


...
}

反应路由器 V5

如果希望路径名作为字符串(’/’或’用户’) ,可以使用以下内容:

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

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是否存在。

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

使用 useLocation() Hook 检测 URL 变化并将其放入 useEffect()中的依赖数组,这个技巧对我很有效

const App = () => {


const location = useLocation();


useEffect(() => {
window.scroll(0,0);
}, [location]);


return (
<React.Fragment>
<Routes>
<Route path={"/"} element={<Template/>} >
<Route index={true} element={<Home/>} />
<Route path={"cart"} element={<Cart/>} />
<Route path={"signin"} element={<Signin/>} />
<Route path={"signup"} element={<Signup/>} />
<Route path={"product/:slug"} element={<Product/>} />
<Route path={"category/:category"} element={<ProductList/>} />
</Route>
</Routes>
</React.Fragment>
);
}


export default App;