ตรวจจับการเปลี่ยนเส้นทางด้วย react-router


91

ฉันต้องใช้ตรรกะทางธุรกิจบางอย่างขึ้นอยู่กับประวัติการเข้าชม

สิ่งที่ฉันต้องการทำมีดังนี้:

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

มีวิธีใดบ้างในการรับการติดต่อกลับจาก react-router เมื่อ URL ได้รับการอัปเดต


คุณกำลังใช้ react-router รุ่นใดอยู่ นั่นจะเป็นตัวกำหนดแนวทางที่ดีที่สุด เราจะให้คำตอบเมื่อคุณอัปเดต ดังที่กล่าวมาwithRouter HoC น่าจะเป็นทางออกที่ดีที่สุดของคุณในการแจ้งตำแหน่งส่วนประกอบ จะอัปเดตส่วนประกอบของคุณด้วย ({การจับคู่ประวัติและตำแหน่ง}) ใหม่ทุกครั้งที่มีการเปลี่ยนแปลงเส้นทาง ด้วยวิธีนี้คุณไม่จำเป็นต้องสมัครสมาชิกและยกเลิกการสมัครกิจกรรมด้วยตนเอง หมายความว่าง่ายต่อการใช้กับส่วนประกอบที่ไม่มีสถานะที่ใช้งานได้และส่วนประกอบของคลาส
Kyle Richardson

คำตอบ:


120

คุณสามารถใช้history.listen()ฟังก์ชันเมื่อพยายามตรวจจับการเปลี่ยนแปลงเส้นทาง พิจารณาว่าคุณกำลังใช้งานอยู่react-router v4ให้ห่อส่วนประกอบของคุณด้วยwithRouterHOC เพื่อเข้าถึงhistoryเสา

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

ออบเจ็กต์ตำแหน่งใช้ส่วนย่อยของอินเทอร์เฟซ 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 ปัจจุบันอย่างไร

เมื่อคุณใช้ react-router 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 "
Kyle Richardson

1
@KyleRichardson ฉันคิดว่าคุณเข้าใจผิดอีกครั้งฉันต้องทำงานกับภาษาอังกฤษของฉันแน่นอน ฉันหมายความว่าหากคุณใช้ react-router v4 และคุณกำลังใช้วัตถุประวัติคุณต้องห่อส่วนประกอบของคุณด้วยwithRouter
Shubham Khatri

@KyleRichardson ฉันเห็นคำตอบที่สมบูรณ์ของฉันฉันได้เพิ่มวิธีการทำใน v3 ด้วย อีกอย่างหนึ่ง OP แสดงความคิดเห็นว่าเขาใช้ v3 วันนี้และฉันได้ตอบคำถามเมื่อวานนี้
Shubham Khatri

1
@ShubhamKhatri ใช่ แต่คำตอบของคุณอ่านผิด เขาไม่ได้ใช้ v4 ... นอกจากนี้ทำไมคุณถึงใช้history.listen()เมื่อใช้withRouterแล้วอัปเดตส่วนประกอบของคุณด้วยอุปกรณ์ประกอบฉากใหม่ทุกครั้งที่เกิดการกำหนดเส้นทาง คุณสามารถทำเปรียบเทียบที่เรียบง่ายของnextProps.location.href === this.props.location.hrefในการcomponentWillUpdateที่จะดำเนินการสิ่งที่คุณต้องการจะทำอย่างไรถ้ามีการเปลี่ยนแปลง
Kyle Richardson

1
@Aris คุณได้รับการเปลี่ยนแปลงเพื่อลองหรือไม่
Shubham Khatri

38

อัปเดตสำหรับ 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

หากคุณต้องการฟัง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';


11

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 ทั้งหมดจะถูกทริกเกอร์ ฉันจะตรวจสอบได้อย่างไรว่าตำแหน่งนี้ถูกต้องสำหรับส่วนประกอบเฉพาะที่จะแสดง
Kex

1
เฮ้ @Kex - เพื่อชี้แจงlocationที่นี่คือตำแหน่งของเบราว์เซอร์ดังนั้นจึงเหมือนกันในทุกองค์ประกอบและถูกต้องเสมอในแง่นั้น หากคุณใช้เบ็ดในส่วนประกอบต่าง ๆ พวกเขาทั้งหมดจะได้รับค่าเดียวกันเมื่อตำแหน่งเปลี่ยนไป ฉันเดาว่าพวกเขาทำอะไรกับข้อมูลนั้นจะแตกต่างกัน แต่ก็สอดคล้องกันเสมอ
davnicwil

ที่สมเหตุสมผล แค่สงสัยว่าคอมโพเนนต์จะรู้ได้อย่างไรว่าการเปลี่ยนแปลงสถานที่นั้นเกี่ยวข้องกับการดำเนินการของตัวเอง เช่นคอมโพเนนต์ได้รับแดชบอร์ด / รายการ แต่จะรู้ได้อย่างไรว่ามันเชื่อมโยงกับตำแหน่งนั้นหรือไม่?
Kex

เว้นแต่ฉันจะทำบางอย่างเช่น if (location.pathName ===“ dashboard / list”) {..... actions} ดูเหมือนเส้นทางการเข้ารหัสแบบแข็งที่สวยงามมากไปยังส่วนประกอบไม่ได้
Kex

8

นี่เป็นคำถามเก่าและฉันไม่ค่อยเข้าใจความจำเป็นของธุรกิจในการรับฟังการเปลี่ยนแปลงเส้นทางเพื่อผลักดันการเปลี่ยนเส้นทาง ดูเหมือนวงเวียน

แต่ถ้าคุณจบลงที่นี่เพราะทั้งหมดที่คุณต้องการก็คือการปรับปรุง'page_path'เปลี่ยนแปลงเส้นทางในการตอบสนองเราเตอร์สำหรับ Google Analytics / แท็กเว็บไซต์ทั่วโลก / สิ่งที่คล้ายกันที่นี่เป็นตะขอที่คุณสามารถใช้ในขณะนี้ ฉันเขียนตามคำตอบที่ยอมรับ:

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ใช้ order: -1;CSS ดังนั้นในรหัสหลอก:

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

หากคุณมีความคิดความคิดเห็นหรือเคล็ดลับเกี่ยวกับโซลูชันนี้โปรดเพิ่มความคิดเห็น


1

ตอบสนองเราเตอร์ V5.0

หากคุณต้องการให้ 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])
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.