我需要根据浏览历史记录实现一些业务逻辑。
我想要做的事情就像这样:
reactRouter.onUrlChange(url => {
this.history.push(url);
});
当URL更新时,有没有办法从react-router接收回调?
我需要根据浏览历史记录实现一些业务逻辑。
我想要做的事情就像这样:
reactRouter.onUrlChange(url => {
this.history.push(url);
});
当URL更新时,有没有办法从react-router接收回调?
当您尝试检测路由更改时,可以利用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
来监听当前位置的变化:
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对象实现了窗口位置接口的一个子集,其中包括:
**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
中支持)
动作是根据用户如何到达当前URL而确定的,可能是PUSH, REPLACE或POP
之一。
当您使用react-router 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>
)
}
}
react-router v4
_”。 - Kyle RichardsonwithRouter
来包装你的组件。 - Shubham KhatriwithRouter
时,为什么要使用history.listen()
,它已经在每次路由发生时使用新的props更新了您的组件?您可以在componentWillUpdate
中进行nextProps.location.href === this.props.location.href
的简单比较,以执行任何需要执行的操作,如果它已更改。 - Kyle Richardsonimport { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function SomeComponent() {
const location = useLocation();
useEffect(() => {
console.log('Location changed');
}, [location]);
...
}
useEffect
中执行history.push()
,则会导致无限循环,因此我不认为这是一个解决方案。 - Cristian Muscalulocation
替换为location.pathname
。 - undefinedreact-router v6
在 react-router v6 中,可以通过组合 useLocation
和 useEffect
钩子来实现此操作。
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
。
location
是浏览器位置,因此在每个组件中都是相同的,并且在这个意义上始终正确。如果您在不同的组件中使用该钩子,当位置发生变化时,它们都将接收到相同的值。我猜他们对这些信息的处理方式会有所不同,但它始终是一致的。 - davnicwiluseLocation
/useEffect
方法有助于解决单页 Ionic 5 React 项目(使用 useParams)中“第一次加载后没有更改”的问题。在第一次加载后,Ionic 生命周期方法都不会被触发。 - naaman如果您想全局监听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';
),这会更好。
这是一个老问题,我不太理解需要监听路由变化并推送路由变化的业务需求;似乎有些绕弯子。
但是,如果您来到这里是因为您想要在react-router路由变化时更新'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>
)
使用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;
您可以在类组件中使用 componentDidUpdate
与 useLocation
来获取路由变化,而在函数式组件中则可以使用 useEffect
在类组件中
import { useLocation } from "react-router";
class MainApp extends React.Component {
constructor(props) {
super(props);
}
async componentDidUpdate(prevProps) {
if(this.props.location.pathname !== prevProps.location.pathname)
{
//route has been changed. do something here
}
}
}
function App() {
const location = useLocation()
return <MainApp location={location} />
}
在函数式组件中
function App() {
const location = useLocation()
useEffect(() => {
//route change detected. do something here
}, [location]) //add location in dependency. It detects the location change
return <Routes>
<Route path={"/"} element={<Home/>} >
<Route path={"login"} element={<Login/>} />
</Routes>
}
withRouter()
和componentDidUpdate()
生命周期方法,在导航到新的URL路径时触发点击以使ChromeVox聚焦于所需元素。
我创建了一个“屏幕”组件,它包装在 react-router switch 标签周围,其中包含所有应用程序的屏幕。
<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>
内容之后呈现,通过css order: -1;
进行视觉定位。因此,在伪代码中:<Screen style={{ display: 'flex' }}>
<main>
<nav style={{ order: -1 }}>
<Screen>
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])
}
React Router V5
如果你想要将路径名作为字符串('/'或'users')使用,可以使用以下代码:
// React Hooks: React Router DOM
let history = useHistory();
const location = useLocation();
const pathName = location.pathname;