Deteksi Perubahan Rute dengan react-router


91

Saya harus menerapkan beberapa logika bisnis tergantung pada riwayat penelusuran.

Yang ingin saya lakukan adalah seperti ini:

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

Apakah ada cara untuk menerima panggilan balik dari react-router ketika URL diperbarui?


Versi react-router apa yang Anda gunakan? Itu akan menentukan pendekatan terbaik. Saya akan memberikan jawaban setelah Anda memperbarui. Karena itu, withRouter HoC mungkin adalah taruhan terbaik Anda untuk membuat lokasi komponen sadar. Ini akan memperbarui komponen Anda dengan yang baru ({match, history, and location}) setiap kali ada perubahan rute. Dengan cara ini Anda tidak perlu berlangganan dan berhenti berlangganan acara secara manual. Artinya mudah digunakan dengan komponen stateless fungsional serta komponen kelas.
Kyle Richardson

Jawaban:


120

Anda dapat menggunakan history.listen()fungsi saat mencoba mendeteksi perubahan rute. Mengingat Anda sedang menggunakan react-router v4, bungkus komponen Anda dengan withRouterHOC untuk mendapatkan akses ke historyprop.

history.listen()mengembalikan suatu unlistenfungsi. Anda akan menggunakan ini untuk unregistermendengarkan.

Anda dapat mengonfigurasi rute Anda seperti

index.js

ReactDOM.render(
      <BrowserRouter>
            <AppContainer>
                   <Route exact path="/" Component={...} />
                   <Route exact path="/Home" Component={...} />
           </AppContainer>
        </BrowserRouter>,
  document.getElementById('root')
);

dan kemudian di 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);

Dari dokumen sejarah :

Anda dapat mendengarkan perubahan lokasi saat ini menggunakan 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}`)
})

Objek lokasi mengimplementasikan subset dari antarmuka window.location, termasuk:

**location.pathname** - The path of the URL
**location.search** - The URL query string
**location.hash** - The URL hash fragment

Lokasi mungkin juga memiliki properti berikut:

location.state - Beberapa status ekstra untuk lokasi ini yang tidak berada di URL (didukung di createBrowserHistorydan createMemoryHistory)

location.key- String unik yang mewakili lokasi ini (didukung di createBrowserHistorydan createMemoryHistory)

Tindakannya PUSH, REPLACE, or POPbergantung pada bagaimana pengguna sampai ke URL saat ini.

Ketika Anda menggunakan react-router v3 Anda dapat menggunakan history.listen()dari historypaket seperti yang disebutkan di atas atau Anda juga dapat menggunakannyabrowserHistory.listen()

Anda dapat mengonfigurasi dan menggunakan rute Anda seperti

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

dia menggunakan v3 dan kalimat kedua dari jawaban Anda mengatakan " Mengingat Anda sedang menggunakanreact-router v4 "
Kyle Richardson

1
@KyleRichardson Saya pikir Anda salah paham lagi, saya pasti harus melatih bahasa Inggris saya. Maksud saya jika Anda menggunakan react-router v4 dan Anda menggunakan objek riwayat maka Anda perlu membungkus komponen Anda denganwithRouter
Shubham Khatri

@KyleRichardson Saya Anda melihat jawaban lengkap saya, saya telah menambahkan cara untuk melakukannya di v3 juga. Satu hal lagi, OP berkomentar bahwa dia menggunakan v3 hari ini dan saya telah menjawab pertanyaan itu kemarin
Shubham Khatri

1
@ShubhamKhatri Ya, tetapi jawaban Anda salah. Dia tidak menggunakan v4 ... Juga, mengapa Anda menggunakan history.listen()saat menggunakan withRoutersudah memperbarui komponen Anda dengan alat peraga baru setiap kali perutean terjadi? Anda bisa melakukan perbandingan sederhana dari nextProps.location.href === this.props.location.hrefdalam componentWillUpdateuntuk melakukan sesuatu yang perlu Anda lakukan jika itu telah berubah.
Kyle Richardson

1
@Aris, apakah Anda mendapat perubahan untuk mencobanya
Shubham Khatri

38

Pembaruan untuk 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>
  );
};

14

Jika Anda ingin mendengarkan historyobjek secara global, Anda harus membuatnya sendiri dan meneruskannya ke Router. Kemudian Anda dapat mendengarkannya dengan listen()metodenya:

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

Lebih baik lagi jika Anda membuat objek riwayat sebagai modul, sehingga Anda dapat dengan mudah mengimpornya di mana pun Anda membutuhkannya (mis import history from './history';


11

react-router v6

Di v6 mendatang , ini bisa dilakukan dengan menggabungkan hook useLocationdanuseEffect

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])
  ...
}

Untuk penggunaan kembali yang nyaman, Anda dapat melakukannya di useLocationChangehook khusus

// 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) 
  })
  ...
}

Jika Anda juga perlu melihat rute sebelumnya pada perubahan, Anda dapat menggabungkan dengan usePreviouskail

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

Penting untuk dicatat bahwa semua api di atas pada rute klien pertama sedang dipasang, serta perubahan selanjutnya. Jika itu masalah, gunakan contoh terakhir dan periksa apakah prevLocationada sebelum melakukan apa pun.


Saya punya pertanyaan. Jika beberapa komponen telah dirender dan semuanya mengawasi useLocation, maka semua useEffects mereka akan dipicu. Bagaimana cara memverifikasi bahwa lokasi ini benar untuk komponen tertentu yang akan ditampilkan?
Kex

1
Hey @Kex - hanya untuk memperjelas di locationsini adalah lokasi browser, jadi sama di setiap komponen dan selalu benar dalam hal itu. Jika Anda menggunakan hook di berbagai komponen, mereka semua akan menerima nilai yang sama saat lokasi berubah. Saya kira apa yang mereka lakukan dengan info itu akan berbeda, tetapi selalu konsisten.
davnicwil

Itu masuk akal. Hanya bertanya-tanya bagaimana komponen akan mengetahui jika perubahan lokasi relevan dengan dirinya sendiri yang melakukan suatu tindakan. Misalnya sebuah komponen menerima dasbor / daftar tetapi bagaimana ia tahu apakah itu terkait dengan lokasi itu atau tidak?
Kex

Kecuali saya melakukan sesuatu seperti if (location.pathName === “dashboard / list”) {..... actions}. Tampaknya tidak ada jalur hardcoding yang sangat elegan ke suatu komponen.
Kex

8

Ini adalah pertanyaan lama dan saya tidak begitu memahami kebutuhan bisnis untuk mendengarkan perubahan rute untuk mendorong perubahan rute; sepertinya bundaran.

TAPI jika Anda berakhir di sini karena yang Anda inginkan hanyalah memperbarui 'page_path'perubahan rute react-router untuk google analytics / tag situs global / sesuatu yang serupa, inilah hook yang sekarang dapat Anda gunakan. Saya menulisnya berdasarkan jawaban yang diterima:

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

Anda harus menggunakan pengait ini sekali di aplikasi Anda, di suatu tempat di dekat bagian atas tetapi masih di dalam router. Saya memilikinya di App.jsyang terlihat seperti ini:

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

Saya menemukan pertanyaan ini saat saya mencoba memfokuskan pembaca layar ChromeVox ke bagian atas "layar" setelah menavigasi ke layar baru di aplikasi satu halaman React. Pada dasarnya mencoba meniru apa yang akan terjadi jika halaman ini dimuat dengan mengikuti link ke halaman web baru yang dirender server.

Solusi ini tidak memerlukan pendengar, ia menggunakan withRouter()dan componentDidUpdate()metode siklus hidup untuk memicu klik untuk memfokuskan ChromeVox ke elemen yang diinginkan saat menavigasi ke jalur url baru.


Penerapan

Saya membuat komponen "Layar" yang dibungkus di sekitar tag saklar react-router yang berisi semua layar aplikasi.

<Screen>
  <Switch>
    ... add <Route> for each screen here...
  </Switch>
</Screen>

Screen.tsx Komponen

Catatan: Komponen ini menggunakan 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)

Saya telah mencoba menggunakan focus()alih-alih click(), tetapi klik menyebabkan ChromeVox berhenti membaca apa pun yang sedang dibaca dan mulai lagi di mana saya menyuruhnya untuk memulai.

Catatan lanjutan: Dalam solusi ini, navigasi <nav>yang berada di dalam komponen Layar dan dirender setelah <main>konten secara visual ditempatkan di atas mainmenggunakan css order: -1;. Jadi dalam kode pseudo:

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

Jika Anda memiliki pemikiran, komentar, atau tip tentang solusi ini, silakan tambahkan komentar.


1

Bereaksi Router V5

Jika Anda ingin pathName sebagai string ('/' atau 'users'), Anda dapat menggunakan berikut ini:

  // 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])
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.