Muszę zaimplementować logikę biznesową w zależności od historii przeglądania.
To, co chcę zrobić, to coś takiego:
reactRouter.onUrlChange(url => {
this.history.push(url);
});
Czy istnieje sposób na otrzymanie oddzwaniania z routera reaktywnego po zaktualizowaniu adresu URL?
Odpowiedzi:
Możesz skorzystać z
history.listen()
funkcji, próbując wykryć zmianę trasy. Biorąc pod uwagę, że używaszreact-router v4
, zapakuj swój komponent za pomocąwithRouter
HOC, aby uzyskać dostęp do właściwościhistory
.history.listen()
zwracaunlisten
funkcję. Używałbyś tegounregister
ze słuchania.Możesz skonfigurować swoje trasy, takie jak
index.js
ReactDOM.render( <BrowserRouter> <AppContainer> <Route exact path="/" Component={...} /> <Route exact path="/Home" Component={...} /> </AppContainer> </BrowserRouter>, document.getElementById('root') );
a następnie w 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);
Z dokumentów historii :
Kiedy używasz React-router v3, możesz skorzystać
history.listen()
zhistory
pakietu z pakietu, jak wspomniano powyżej, lub możesz również skorzystaćbrowserHistory.listen()
Możesz konfigurować i używać swoich tras, takich jak
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> ) } }
źródło
react-router v4
”withRouter
history.listen()
gdy używaszwithRouter
już aktualizacji komponentu o nowe rekwizyty za każdym razem, gdy występuje routing? Możesz zrobić proste porównanienextProps.location.href === this.props.location.href
w,componentWillUpdate
aby wykonać wszystko, co musisz zrobić, jeśli uległo zmianie.Aktualizacja dla 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> ); };
źródło
Jeśli chcesz słuchać
history
obiektu globalnie, musisz sam go stworzyć i przekazać doRouter
. Następnie możesz posłuchać jejlisten()
metodą:// 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>
Jeszcze lepiej, jeśli utworzysz obiekt historii jako moduł, dzięki czemu możesz go łatwo zaimportować w dowolnym miejscu (np
import history from './history';
źródło
react-router v6
W nadchodzącym v6 , można to zrobić przez połączenie
useLocation
iuseEffect
hakiimport { 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]) ... }
W celu wygodnego ponownego wykorzystania możesz to zrobić w niestandardowym
useLocationChange
haku// 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) }) ... }
Jeśli chcesz również zobaczyć poprzednią trasę przy zmianie, możesz połączyć ją z
usePrevious
hakiemconst 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) }) ... }
Ważne jest, aby pamiętać, że wszystkie powyższe wywołują pożar na pierwszej montowanej trasie klienta, a także kolejne zmiany. Jeśli to problem, skorzystaj z drugiego przykładu i sprawdź, czy
prevLocation
istnieje, zanim cokolwiek zrobisz.źródło
location
tutaj jest lokalizacja przeglądarki, więc jest taka sama w każdym komponencie i zawsze poprawna w tym sensie. Jeśli użyjesz haka w różnych komponentach, wszystkie otrzymają te same wartości po zmianie lokalizacji. Myślę, że to, co zrobią z tymi informacjami, będzie inne, ale zawsze jest spójne.To stare pytanie i nie do końca rozumiem biznesową potrzebę nasłuchiwania zmian tras w celu wprowadzenia zmian; wydaje się okrężna.
ALE jeśli trafiłeś tutaj, ponieważ wszystko, czego chciałeś, to zaktualizować
'page_path'
zmianę trasy na routerze reagującym na Google Analytics / globalny tag witryny / coś podobnego, oto haczyk, którego możesz teraz użyć. Napisałem to na podstawie zaakceptowanej odpowiedzi: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]) }
Powinieneś użyć tego haka raz w swojej aplikacji, gdzieś u góry, ale nadal wewnątrz routera. Mam to na,
App.js
który wygląda tak: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> )
źródło
Natknąłem się na to pytanie, gdy próbowałem ustawić czytnik ekranu ChromeVox na górze „ekranu” po przejściu do nowego ekranu w aplikacji jednostronicowej React. Zasadniczo próba emulacji tego, co by się stało, gdyby ta strona została załadowana, podążając za linkiem do nowej strony internetowej renderowanej przez serwer.
To rozwiązanie nie wymaga żadnych odbiorników, wykorzystuje
withRouter()
icomponentDidUpdate()
metodę cyklu życia, aby wywołać kliknięcie, aby skupić ChromeVox na żądanym elemencie podczas przechodzenia do nowej ścieżki adresu URL.Realizacja
Utworzyłem komponent „Screen”, który jest owinięty wokół tagu przełącznika reagującego na router, który zawiera wszystkie ekrany aplikacji.
<Screen> <Switch> ... add <Route> for each screen here... </Switch> </Screen>
Screen.tsx
SkładnikUwaga: ten składnik używa 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)
Próbowałem użyć
focus()
zamiastclick()
, ale kliknięcie powoduje, że ChromeVox przestaje czytać to, co aktualnie czyta, i zaczyna od nowa od miejsca, w którym każę mu zacząć.Uwaga zaawansowana: w tym rozwiązaniu nawigacja
<nav>
wewnątrz komponentu Screen i renderowana po<main>
treści jest wizualnie umieszczana nadmain
using cssorder: -1;
. Czyli w pseudokodzie:<Screen style={{ display: 'flex' }}> <main> <nav style={{ order: -1 }}> <Screen>
Jeśli masz jakieś przemyślenia, uwagi lub wskazówki dotyczące tego rozwiązania, dodaj komentarz.
źródło
React Router V5
Jeśli chcesz, aby pathName był ciągiem („/” lub „users”), możesz użyć następującego:
// React Hooks: React Router DOM let history = useHistory(); const location = useLocation(); const pathName = location.pathname;
źródło
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]) }
źródło