Mam kilka przycisków, które działają jak trasy. Za każdym razem, gdy zmienia się trasa, chcę się upewnić, że aktywny przycisk się zmienia.
Czy istnieje sposób, aby nasłuchiwać zmian tras w React Router v4?
Odpowiedzi:
Używam withRouter
do zdobycia location
rekwizytu. Kiedy komponent jest aktualizowany z powodu nowej trasy, sprawdzam, czy zmieniła się wartość:
@withRouter
class App extends React.Component {
static propTypes = {
location: React.PropTypes.object.isRequired
}
// ...
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}
// ...
render(){
return <Switch>
<Route path="/" exact component={HomePage} />
<Route path="/checkout" component={CheckoutPage} />
<Route path="/success" component={SuccessPage} />
// ...
<Route component={NotFound} />
</Switch>
}
}
Mam nadzieję, że to pomoże
withRouter
, ale pojawia się błąd You should not use <Route> or withRouter() outside a <Router>
. Nie widzę żadnego <Router/>
komponentu w powyższym kodzie. Jak to działa?
<Switch>
komponent działa jako de facto router. <Route>
Wyrenderowany zostanie tylko pierwszy wpis, który ma pasującą ścieżkę. W <Router/>
tym scenariuszu nie jest potrzebny żaden komponent
Aby rozwinąć powyższe, musisz dostać się do obiektu historii. Jeśli używasz BrowserRouter
, możesz zaimportować withRouter
i opakować swój komponent komponentem wyższego rzędu (HoC) , aby mieć dostęp poprzez właściwości do właściwości i funkcji obiektu historii.
import { withRouter } from 'react-router-dom';
const myComponent = ({ history }) => {
history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state)
});
return <div>...</div>;
};
export default withRouter(myComponent);
Jedyną rzeczą, o której należy pamiętać, jest to, że przy użyciu routera i większości innych sposobów uzyskiwania dostępu do obiektów history
zdaje się, że zanieczyszczają one właściwości, gdy przekształcają one obiekt w jego strukturę.
withRoutes
się withRouter
.
Powinieneś użyć historii v4 lib.
Przykład z tego miejsca
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
history.push
wyzwala history.listen
. Zobacz przykład użycia podstawowego adresu URL w dokumentach historii v4 . Ponieważ history
jest to w rzeczywistości opakowanie natywnego history
obiektu przeglądarki, nie zachowuje się dokładnie tak, jak natywny.
const unlisten = history.listen(myListener); unlisten();
withRouter
, history.listen
i useEffect
(React Hooks) całkiem dobrze razem współpracują:
import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
const Component = ({ history }) => {
useEffect(() => history.listen(() => {
// do something on route change
// for my example, close a drawer
}), [])
//...
}
export default withRouter(Component)
Wywołanie zwrotne listenera zostanie uruchomione za każdym razem, gdy zostanie zmieniona trasa, a zwrotem dla history.listen
jest program obsługi zamykania, z którym dobrze się bawi useEffect
.
Wersja 5.1 wprowadza przydatny hak useLocation
https://reacttraining.com/blog/react-router-v5-1/#uselocation
import { Switch, useLocation } from 'react-router-dom'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
Cannot read property 'location' of undefined at useLocation
. Musisz się upewnić, że wywołanie useLocation () nie znajduje się w tym samym komponencie, który umieszcza router w drzewie: patrz tutaj
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';
function MyApp() {
const location = useLocation();
useEffect(() => {
console.log('route has been changed');
...your code
},[location.pathname]);
}
z haczykami
react-router-dom
zamiastreact-router
Z haczykami:
import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'
const DebugHistory = ({ history }) => {
useEffect(() => {
console.log('> Router', history.action, history.location])
}, [history.location.key])
return null
}
DebugHistory.propTypes = { history: historyShape }
export default withRouter(DebugHistory)
Importuj i renderuj jako <DebugHistory>
komponent
Korzystam z React Hooks useEffect
const history = useHistory()
const queryString = require('query-string')
const parsed = queryString.parse(location.search)
const [search, setSearch] = useState(parsed.search ? parsed.search : '')
useEffect(() => {
const parsedSearch = parsed.search ? parsed.search : ''
if (parsedSearch !== search) {
// do some action! The route Changed!
}
}, [location.search])
W przypadku komponentów funkcjonalnych spróbuj useEffect z props.location.
import React, {useEffect} from 'react';
const SampleComponent = (props) => {
useEffect(() => {
console.log(props.location);
}, [props.location]);
}
export default SampleComponent;
W niektórych przypadkach możesz użyć render
atrybutu zamiast component
, w ten sposób:
class App extends React.Component {
constructor (props) {
super(props);
}
onRouteChange (pageId) {
console.log(pageId);
}
render () {
return <Switch>
<Route path="/" exact render={(props) => {
this.onRouteChange('home');
return <HomePage {...props} />;
}} />
<Route path="/checkout" exact render={(props) => {
this.onRouteChange('checkout');
return <CheckoutPage {...props} />;
}} />
</Switch>
}
}
Zwróć uwagę, że jeśli zmienisz stan onRouteChange
metody, może to spowodować błąd „Przekroczono maksymalną głębokość aktualizacji”.
Dzięki useEffect
hakowi można wykryć zmiany trasy bez dodawania nasłuchiwania.
import React, { useEffect } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import Main from './Main';
import Blog from './Blog';
const App = ({history}) => {
useEffect( () => {
// When route changes, history.location.pathname changes as well
// And the code will execute after this line
}, [history.location.pathname]);
return (<Switch>
<Route exact path = '/' component = {Main}/>
<Route exact path = '/blog' component = {Blog}/>
</Switch>);
}
export default withRouter(App);
Właśnie poradziłem sobie z tym problemem, więc dodam moje rozwiązanie jako uzupełnienie innych udzielonych odpowiedzi.
Problem polega na tym, useEffect
że nie działa tak, jak byś tego chciał, ponieważ wywołanie jest wyzwalane dopiero po pierwszym renderowaniu, więc występuje niepożądane opóźnienie.
Jeśli używasz jakiegoś menedżera stanu, takiego jak redux, istnieje prawdopodobieństwo, że pojawi się migotanie na ekranie z powodu utrzymującego się stanu w sklepie.
To, czego naprawdę chcesz, to użyć useLayoutEffect
ponieważ jest to uruchamiane natychmiast.
Napisałem więc małą funkcję narzędzia, którą umieściłem w tym samym katalogu co mój router:
export const callApis = (fn, path) => {
useLayoutEffect(() => {
fn();
}, [path]);
};
Które dzwonię z poziomu HOC komponentu w ten sposób:
callApis(() => getTopicById({topicId}), path);
path
to właściwość, która jest przekazywana w match
obiekcie podczas używania withRouter
.
Nie jestem zwolennikiem ręcznego odsłuchiwania / nieposłuchiwania historii. To tylko imo.