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 withRouterdo zdobycia locationrekwizytu. 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ć withRouteri 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 historyzdaje się, że zanieczyszczają one właściwości, gdy przekształcają one obiekt w jego strukturę.
withRoutessię 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.pushwyzwala history.listen. Zobacz przykład użycia podstawowego adresu URL w dokumentach historii v4 . Ponieważ historyjest to w rzeczywistości opakowanie natywnego historyobiektu przeglądarki, nie zachowuje się dokładnie tak, jak natywny.
const unlisten = history.listen(myListener); unlisten();
withRouter, history.listeni 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.listenjest 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-domzamiastreact-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ć renderatrybutu 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 onRouteChangemetody, może to spowodować błąd „Przekroczono maksymalną głębokość aktualizacji”.
Dzięki useEffecthakowi 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);
pathto właściwość, która jest przekazywana w matchobiekcie podczas używania withRouter.
Nie jestem zwolennikiem ręcznego odsłuchiwania / nieposłuchiwania historii. To tylko imo.