React-router ile Rota Değişikliğini Algılama


91

Tarama geçmişine bağlı olarak bazı iş mantığı uygulamalıyım.

Yapmak istediğim şey şuna benzer:

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

URL güncellendiğinde react-router'dan geri arama almanın bir yolu var mı?


React-router'ın hangi sürümünü kullanıyorsunuz? Bu, en iyi yaklaşımı belirleyecektir. Güncellediğinizde bir cevap vereceğim. Bununla birlikte, withRouter HoC, bir bileşen konumunun farkında olmak için muhtemelen en iyi seçeneğinizdir. Bir rota her değiştiğinde bileşeninizi yenisiyle ({maç, geçmiş ve konum}) güncelleyecektir. Bu şekilde, etkinliklere manuel olarak abone olmanıza ve abonelikten çıkmanıza gerek kalmaz. Bunun anlamı, sınıf bileşenlerinin yanı sıra işlevsel durumsuz bileşenlerle kullanımı kolaydır.
Kyle Richardson

Yanıtlar:


120

Sen yararlanabilir history.listen()rota değişikliği algılamak için çalışırken işlevi. Kullandığınızı göz önünde bulundurarak , pervaneye erişmek için react-router v4bileşeninizi withRouterHOC ile sarın history.

history.listen()bir unlistenişlev döndürür . Bunu unregisterdinlemek için kullanırsın.

Rotalarınızı şu şekilde yapılandırabilirsiniz:

index.js

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

ve ardından AppContainer.js'de

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

Tarih belgelerinden :

Aşağıdakileri kullanarak mevcut konumdaki değişiklikleri dinleyebilirsiniz 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 nesnesi, window.location arayüzünün bir alt kümesini uygular, örneğin:

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

Konumlar ayrıca aşağıdaki özelliklere sahip olabilir:

location.state - Bu konum için URL'de bulunmayan bazı ekstra durumlar ( createBrowserHistoryve içinde desteklenir createMemoryHistory)

location.key- Bu konumu temsil eden benzersiz bir dize ( createBrowserHistoryvecreateMemoryHistory )

İşlem, PUSH, REPLACE, or POPkullanıcının mevcut URL'ye nasıl ulaştığına bağlıdır.

Tepki-yönlendirici kullanıldığında zaman yararlanabilirler V3 history.listen()gelen history, yukarıda belirtildiği gibi bir paket veya da yararlanabilirbrowserHistory.listen()

Rotalarınızı şu şekilde yapılandırabilir ve kullanabilirsiniz:

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

o v3 ve cevabın ikinci cümleyi kullanan var "diyor kullandığınız göz önüne alındığındareact-router v4 "
Kyle Richardson

1
@KyleRichardson Sanırım beni yine yanlış anladın, kesinlikle ingilizcem üzerinde çalışmalıyım. React-router v4 kullanıyorsanız ve geçmiş nesnesi kullanıyorsanız, bileşeniniziwithRouter
Shubham Khatri

@KyleRichardson Tam cevabımı görüyorsunuz, bunu v3'te de yapmanın yollarını ekledim. Bir şey daha, OP onun bugün v3 kullandığını söyledi ve dün soruyu cevaplamıştım
Shubham Khatri

1
@ShubhamKhatri Evet ama cevabınızın okuma şekli yanlış. O neden kullanmak istiyorsunuz ... Ayrıca, v4 kullanmıyor history.listen()kullanırken withRouterzaten her zaman yönlendirme gerçekleşir yeni sahne ile bileşenini günceller? Değiştiyse yapmanız gereken herhangi bir şeyi gerçekleştirmek için nextProps.location.href === this.props.location.hrefin componentWillUpdateile basit bir karşılaştırma yapabilirsiniz.
Kyle Richardson

1
@Aris, denemek için değişiklik yaptın mı
Shubham Khatri

38

React Router 5.1 için güncelleme.

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

historyNesneyi global olarak dinlemek istiyorsanız , onu kendiniz yaratmanız ve Router. Daha sonra listen()yöntemiyle dinleyebilirsiniz :

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

Geçmiş nesnesini bir modül olarak oluşturursanız daha da iyi, böylece ihtiyacınız olan her yere kolayca içe aktarabilirsiniz (örn. import history from './history';


11

react-router v6

Gelecek v6'da bu, useLocationve useEffectkancaları birleştirerek yapılabilir.

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

Uygun yeniden kullanım için bunu özel bir useLocationChangekancada yapabilirsiniz

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

Değişiklik sırasında önceki rotayı da görmeniz gerekiyorsa, bir usePreviouskancayla birleştirebilirsiniz.

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

İlk müşteri rotasında yukarıdaki tüm yangının ve sonraki değişikliklerin olduğuna dikkat etmek önemlidir . Bu bir sorunsa, ikinci örneği kullanın ve prevLocationherhangi bir şey yapmadan önce bir var olup olmadığını kontrol edin .


Bir sorum var. Birkaç bileşen oluşturulmuşsa ve hepsi useLocation'ı izliyorsa, tüm useEffects tetiklenecektir. Bu konumun görüntülenecek belirli bileşen için doğru olduğunu nasıl doğrularım?
Kex

1
Hey @Kex - locationburada açıklığa kavuşturmak için tarayıcının konumu, yani her bileşende aynı ve bu anlamda her zaman doğru. Kancayı farklı bileşenlerde kullanırsanız, konum değiştiğinde hepsi aynı değerleri alır. Sanırım bu bilgilerle yaptıkları farklı olacak, ancak her zaman tutarlıdır.
davnicwil

Mantıklı. Bir bileşenin, konum değişikliğinin bir eylemi gerçekleştiren kendisiyle alakalı olup olmadığını nasıl bileceğini merak ediyorum. Örneğin, bir bileşen kontrol panelini / listeyi alır, ancak bu konuma bağlı olup olmadığını nasıl anlar?
Kex

İf (location.pathName === “dashboard / list”) {..... actions} gibi bir şey yapmazsam. Yine de bir bileşene giden çok zarif kodlama yolu görünmüyor.
Kex

8

Bu eski bir sorudur ve bir rota değişikliğini gerçekleştirmek için rota değişikliklerini dinlemenin iş ihtiyacını tam olarak anlamıyorum; dolambaçlı görünüyor.

AMA, tek istediğiniz şey 'page_path'google analytics / global site etiketi / benzer bir şey için bir react-router rota değişikliğini güncellemek olduğu için burada sona erdiyseniz , işte şimdi kullanabileceğiniz bir kanca . Kabul edilen cevaba göre yazdım:

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

Bu kancayı bir kez uygulamanızda, üste yakın bir yerde, ancak yine de bir yönlendiricinin içinde kullanmalısınız. App.jsŞuna benzer bir şeye sahibim :

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

Bir React tek sayfalı uygulamada yeni bir ekrana gittikten sonra ChromeVox ekran okuyucuyu "ekranın" üst kısmına odaklamaya çalışırken bu soruyla karşılaştım. Temel olarak, bu sayfanın sunucu tarafından oluşturulmuş yeni bir web sayfasına bir bağlantı izlenerek yüklenmesi durumunda ne olacağını taklit etmeye çalışmak.

Bu çözüm herhangi bir dinleyici gerektirmez, ChromeVox'u yeni bir url yoluna giderken istenen öğeye odaklamak için bir tıklamayı tetiklemek withRouter()için componentDidUpdate()yaşam döngüsü yöntemini kullanır .


Uygulama

Tüm uygulama ekranlarını içeren react-yönlendirici anahtar etiketinin etrafına sarılmış bir "Ekran" bileşeni oluşturdum.

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

Screen.tsx Bileşen

Not: Bu bileşen React + TypeScript kullanır

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)

Onun focus()yerine kullanmayı denedim click(), ancak tıklama ChromeVox'un şu anda okuduğu şeyi okumayı durdurmasına ve başlamasını söylediğim yerden yeniden başlamasına neden oluyor.

Gelişmiş not: Bu çözümde, <nav>Ekran bileşeninin içinde bulunan ve <main>içeriğin görsel olarak maincss kullanılarak konumlandırılmasının ardından oluşturulan gezinme order: -1;. Yani sözde kodda:

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

Bu çözümle ilgili herhangi bir düşünceniz, yorumunuz veya ipucunuz varsa, lütfen bir yorum ekleyin.


1

React Router V5

YolAdı'nı bir dize ('/' veya 'kullanıcılar') olarak istiyorsanız, aşağıdakileri kullanabilirsiniz:

  // 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])
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.