Відповіді:
Я використовую withRouter
для отримання location
опори. Коли компонент оновлюється через новий маршрут, я перевіряю, чи змінилося значення:
@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>
}
}
Сподіваюся, це допомагає
withRouter
, але я отримую помилку You should not use <Route> or withRouter() outside a <Router>
. Я не бачу жодного <Router/>
компонента у наведеному вище коді. То як це працює?
<Switch>
компонент виступає в ролі маршрутизатора де-факто. Буде відображено лише перший <Route>
запис, який має відповідні шляхи. У <Router/>
цьому сценарії немає потреби в будь-якому компоненті
Щоб розширити вищезазначене, вам потрібно буде потрапити на об’єкт історії. Якщо ви користуєтесь BrowserRouter
, ви можете імпортувати withRouter
та обернути свій компонент компонентом вищого порядку (HoC) , щоб мати доступ через реквізит до властивостей та функцій об’єкта історії.
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);
Єдине, про що слід пам’ятати, це те, що за допомогою рутера та більшості інших способів доступу, history
схоже, забруднюється реквізит, коли вони деструктурують об’єкт в нього.
withRoutes
в withRouter
.
Вам слід використовувати history v4 lib.
Приклад звідти
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
це і спрацьовує history.listen
. Див. Приклад Використання базової URL-адреси в документах історії v4 . Оскільки history
це насправді обгортка рідного history
об’єкта браузера, він не веде себе так, як рідний.
const unlisten = history.listen(myListener); unlisten();
withRouter
, history.listen
І useEffect
(React Пастки) працює досить добре разом:
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)
Зворотний виклик слухача запустить будь-яку зміну маршруту, а повернення history.listen
- це обробник відключення, з яким чудово грає useEffect
.
v5.1 представляє корисний гачок 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
. Вам потрібно переконатися, що виклик useLocation () не в тому ж компоненті, який ставить маршрутизатор у дерево: дивіться тут
З гачками:
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)
Імпорт і візуалізація як <DebugHistory>
компонент
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]);
}
з гачками
Я реагую на Гачки 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])
У деяких випадках ви можете використовувати render
атрибут замість component
, таким чином:
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>
}
}
Зауважте, що якщо ви зміните стан у onRouteChange
методі, це може спричинити помилку "Максимальна глибина оновлення".
За допомогою useEffect
гачка можна виявити зміни маршруту без додавання слухача.
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);
Я щойно розібрався з цією проблемою, тому додам своє рішення як доповнення до інших відповідей.
Проблема тут полягає в тому, що useEffect
насправді не працює так, як ви хотіли б, оскільки виклик спрацьовує лише після першої рендерінгу, тому виникає небажана затримка.
Якщо ви використовуєте такий менеджер штатів, як redux, велика ймовірність, що ви отримаєте мерехтіння на екрані через затяжний стан у магазині.
Те, що ви дійсно хочете, - це використовувати, useLayoutEffect
оскільки це спрацьовує негайно.
Тому я написав невелику функцію утиліти, яку я помістив у той самий каталог, що і мій маршрутизатор:
export const callApis = (fn, path) => {
useLayoutEffect(() => {
fn();
}, [path]);
};
Я називаю всередині компонента HOC так:
callApis(() => getTopicById({topicId}), path);
path
- опора, яка передається в match
об'єкт під час використання withRouter
.
Я не дуже прихильник прослуховування / відключення вручну історії. Це просто іммо.