Виявити зміну маршруту за допомогою маршрутизатора


87

Мені доводиться впроваджувати певну бізнес-логіку залежно від історії перегляду.

Я хочу зробити щось подібне:

reactRouter.onUrlChange(url => {
   this.history.push(url);
});

Чи є спосіб отримати зворотний дзвінок від реакційного маршрутизатора, коли URL-адреса оновлюється?


Яку версію React-Router ви використовуєте? Це визначить найкращий підхід. Я дам відповідь після оновлення. З огляду на це , withRouter HoC - це, мабуть, найкращий вибір для того, щоб усвідомити розташування компонента. Він оновить ваш компонент новими ({match, history, and location}) при будь-якій зміні маршруту. Таким чином, вам не потрібно вручну підписуватися та скасовувати підписку на події. Це означає, що його легко використовувати з функціональними компонентами без стану, а також компонентами класу.
Kyle Richardson

Відповіді:


114

Ви можете скористатися history.listen()функцією, намагаючись виявити зміну маршруту. Враховуючи, що ви використовуєте react-router v4, оберніть ваш компонент withRouterHOC, щоб отримати доступ до historyprop.

history.listen()повертає unlistenфункцію. Ви б використали це, щоб unregisterслухати.

Ви можете налаштувати свої маршрути, як

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 реалізує підмножину інтерфейсу window.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)

Дія PUSH, REPLACE, or POPзалежить від того, як користувач потрапляє до поточної URL-адреси.

При використанні зреагувати-маршрутизатор v3 ви можете використовувати history.listen()з historyпакета , як згадувалося вище , або ви можете також використовувати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>
        )
    }
} 

він використовує v3, а друге речення вашої відповіді говорить " Враховуючи, що ви використовуєтеreact-router v4 "
Кайл Річардсон,

1
@KyleRichardson Я думаю, ти знову мене неправильно зрозумів, я, звичайно, повинен попрацювати над своєю англійською мовою. Я мав на увазі, що якщо ви використовуєте response-router v4 і використовуєте об’єкт історії, то вам потрібно обернути ваш компонентwithRouter
Шубхам Хатрі

@KyleRichardson Я бачу мою повну відповідь, я також додав способи зробити це у v3. Ще одна річ, ОП прокоментував, що він сьогодні використовує v3, і я відповів на це питання вчора
Шубхам Хатрі

1
@ShubhamKhatri Так, але спосіб читання вашої відповіді неправильний. Він не використовує v4 ... Крім того, чому б ви використовували, history.listen()коли використовуєте withRouterвже оновлені компоненти з новими реквізитами кожного разу, коли відбувається маршрутизація? Ви могли б зробити просте порівняння nextProps.location.href === this.props.location.hrefв componentWillUpdateвиконати всі , що вам потрібно зробити , якщо він змінився.
Kyle Richardson

1
@Aris, ти отримав зміни, щоб спробувати
Шубхам Хатрі

36

Оновлення для 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>
  );
};

13

Якщо ви хочете прослухати 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';


9

react-router v6

У майбутньому 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існує, перш ніж щось робити.


У мене є питання. Якщо було відтворено декілька компонентів, і всі вони спостерігають за useLocation, тоді спрацьовуватимуть всі їхні useEffects. Як мені переконатися, що це місце є правильним для певного компонента, який буде відображатися?
Кекс,

1
Привіт @Kex - лише для уточнення locationтут є місцезнаходження браузера, тому воно однакове в усіх компонентах і завжди правильне в цьому сенсі. Якщо ви використовуєте гачок у різних компонентах, вони всі отримають однакові значення при зміні розташування. Я думаю, те, що вони роблять з цією інформацією, буде іншим, але це завжди послідовно.
davnicwil

Що має сенс. Просто цікаво, як компонент дізнається, чи зміна місцезнаходження відповідає самому виконанню дії. Наприклад, компонент отримує інформаційну панель / список, але звідки він знає, прив'язаний він до цього місця чи ні?
Кекс

Якщо я не роблю щось на зразок if (location.pathName === “інформаційна панель / список”) {..... дії}. Однак це здається не дуже елегантним шляхом жорсткого кодування до компонента.
Кекс

8

Це старе запитання, і я не зовсім розумію ділову потребу прослуховування змін маршруту, щоб просунути зміну маршруту; здається круговим рухом.

АЛЕ якщо ви опинилися тут, тому що все, що ви хотіли, - це оновити 'page_path'зміну маршруту маршрутизатора реакцій для аналітики Google / глобального тегу сайту / чогось подібного, ось ось гачок, який ви тепер можете використовувати. Я написав це на основі прийнятої відповіді:

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>
)

1

Я зіткнувся з цим запитанням, коли намагався сфокусувати зчитувач екрана ChromeVox у верхній частині "екрану" після переходу на новий екран у односторінковій програмі React. В основному намагаються наслідувати, що сталося б, якби цю сторінку завантажили, перейшовши за посиланням на нову веб-сторінку, що відображається сервером.

Це рішення не вимагає прослуховувачів, воно використовує withRouter()і componentDidUpdate()метод життєвого циклу, щоб викликати клацання, щоб зосередити ChromeVox на бажаному елементі під час переходу до нового шляху URL-адреси.


Впровадження

Я створив компонент "Екран", який обертається навколо тегу перемикача маршрутизатора, який містить усі екрани програм.

<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>вмісту, візуально розташована над mainвикористанням css order: -1;. Отже, в псевдокоді:

<Screen style={{ display: 'flex' }}>
  <main>
  <nav style={{ order: -1 }}>
<Screen>

Якщо у вас є думки, коментарі чи поради щодо цього рішення, додайте коментар.


1

React Router V5

Якщо ви хочете pathName як рядок ('/' або 'users'), ви можете використовувати наступне:

  // React Hooks: React Router DOM
  let history = useHistory();
  const location = useLocation();
  const pathName = location.pathname;

0
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])
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.