จะฟังการเปลี่ยนแปลงเส้นทางใน react router v4 ได้อย่างไร?


149

ฉันมีปุ่มสองสามปุ่มที่ทำหน้าที่เป็นเส้นทาง ทุกครั้งที่เปลี่ยนเส้นทางฉันต้องการให้แน่ใจว่าปุ่มที่ใช้งานอยู่เปลี่ยนไป

มีวิธีฟังการเปลี่ยนแปลงเส้นทางใน react router v4 หรือไม่?


คำตอบ:


179

ฉันใช้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>
  }
}

หวังว่าจะช่วยได้


22
ใช้ 'this.props.location.pathname' ใน react router v4
ptorsson

4
@ledfusion ฉันกำลังทำแบบเดียวกันและใช้แต่ฉันได้รับข้อผิดพลาดwithRouter You should not use <Route> or withRouter() outside a <Router>ฉันไม่เห็น<Router/>ส่วนประกอบใด ๆในโค้ดด้านบน แล้วมันทำงานอย่างไร?
darKnight

1
สวัสดี @maverick ฉันไม่แน่ใจว่าโค้ดของคุณมีลักษณะอย่างไร แต่ในตัวอย่างด้านบน<Switch>ส่วนประกอบกำลังทำหน้าที่เป็นเราเตอร์โดยพฤตินัย ระบบจะแสดงเฉพาะ<Route>รายการแรกที่มีเส้นทางที่ตรงกัน ไม่จำเป็นต้องมี<Router/>ส่วนประกอบใด ๆในสถานการณ์นี้
brickpop

1
ในการใช้ @withRouter คุณต้องติดตั้ง npm install --save-dev transform-decorators-legacy
Sigex

ว่าเป็นอย่างไรกับเราเตอร์
Jovylle Bermudez

73

หากต้องการขยายด้านบนคุณจะต้องไปที่วัตถุประวัติ หากคุณกำลังใช้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ดูเหมือนว่าจะก่อให้เกิดมลพิษต่ออุปกรณ์ประกอบฉากขณะที่พวกเขาถอดโครงสร้างของวัตถุเข้าไป


1
คำตอบช่วยให้ฉันเข้าใจบางสิ่งโดยไม่คำนึงถึงคำถาม :) แต่แก้ไขwithRoutesเป็นwithRouter.
Sergey Reutskiy

1
ใช่ขอโทษขอบคุณที่ชี้ให้เห็น ฉันได้แก้ไขโพสต์แล้ว ฉันใส่การนำเข้าที่ถูกต้องไว้ที่ด้านบนของคำถามแล้วสะกดผิดในตัวอย่างโค้ด
Sam Parmenter

5
ผมคิดว่ารุ่นปัจจุบันของ withRouterผ่านมากกว่าตัวแปรhistory listen
mikebridge

5
จะเป็นการดีที่จะแก้ไขโพสต์เพื่อแสดงการไม่รับฟัง รหัสนี้มีหน่วยความจำรั่วอยู่
AndrewSouthpaw

คุณควรสมัครเพียงครั้งเดียว! ตอนนี้คุณจะสมัครสมาชิกในการเรนเดอร์แต่ละองค์ประกอบ
Ievgen Naida

35

คุณควรใช้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}`)
})

1
การเรียก history.pushState () และ history.replaceState () ไม่ทริกเกอร์เหตุการณ์ popstate ดังนั้นเพียงอย่างเดียวจะไม่ครอบคลุมการเปลี่ยนแปลงเส้นทางทั้งหมด
Ryan

1
@Ryan มันดูเหมือนว่าจะไม่ทริกเกอร์history.push history.listenดูการใช้ฐาน URLตัวอย่างเช่นในประวัติศาสตร์เอกสาร v4 เนื่องจากนั่นhistoryเป็นกระดาษห่อหุ้มของhistoryออบเจ็กต์ดั้งเดิมของเบราว์เซอร์จึงไม่ทำงานเหมือนกับเนทีฟ
Rockallite

นี่เป็นวิธีแก้ปัญหาที่ดีกว่าบ่อยครั้งที่คุณต้องฟังการเปลี่ยนแปลงเส้นทางสำหรับการผลักดันเหตุการณ์ซึ่งไม่เกี่ยวข้องกับการตอบสนองต่อเหตุการณ์วงจรชีวิตของส่วนประกอบ
Daniel Dubovski

12
ศักยภาพความจำรั่ว! สำคัญมากที่คุณต้องทำสิ่งนี้! "เมื่อคุณแนบ Listener โดยใช้ history.listen มันจะส่งคืนฟังก์ชันที่สามารถใช้เพื่อลบฟังซึ่งสามารถเรียกใช้ในตรรกะการล้างข้อมูล:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos

ไปที่นี่เพื่อดูเอกสารเกี่ยวกับแพ็คเกจประวัติศาสตร์ github.com/ReactTraining/history/blob/master/docs/…
Jason Kim

27

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.listenuseEffect


27

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

10
Cannot read property 'location' of undefined at useLocationเพียงแค่ทราบขณะที่ผมกำลังมีปัญหากับข้อผิดพลาด: คุณต้องตรวจสอบให้แน่ใจว่าการเรียก useLocation () ไม่ได้อยู่ในองค์ประกอบเดียวกับที่วางเราเตอร์ไว้ในทรีดูที่นี่
Toddg

สิ่งนี้ไม่ได้ทำให้เกิดการดำเนินการบนเส้นทางที่ซ้อนกันอย่างน่าเสียดาย
Eugene Kuzmenko

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

}

ด้วยตะขอ


ฮอลลี่เจส! มันทำงานอย่างไร? คำตอบของคุณเด็ด! buti ใส่จุดดีบักเกอร์ไว้useEffectแต่เมื่อใดก็ตามที่ฉันเปลี่ยนชื่อพา ธ ที่ตั้งยังไม่ได้กำหนด ? คุณสามารถแบ่งปันบทความดีๆได้หรือไม่? เพราะยากที่จะหาข้อมูลที่ชัดเจน
AlexNikonov

ใช้react-router-domแทนreact-router
Nolesh

12

ด้วยตะขอ:

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>ส่วนประกอบ


7
import { useHistory } from 'react-router-dom';

const Scroll = () => {
  const history = useHistory();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [history.location.pathname]);

  return null;
}

มันดูการเปลี่ยนแปลงแฮชด้วยหรือไม่? เส้นทาง / a # 1 -> เส้นทาง / a # 2
เร

2

ด้วย react Hooks ฉันใช้ 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])

2

สำหรับส่วนประกอบที่ใช้งานได้ลอง useEffect กับ props.location

import React, {useEffect} from 'react';

const SampleComponent = (props) => {

      useEffect(() => {
        console.log(props.location);
      }, [props.location]);

}

export default SampleComponent;

1
ขอบคุณสำหรับสิ่งนี้. ฉันคิดว่าฉันต้องใช้เวลาหลายชั่วโมงในการดีบักโค้ดของฉัน แต่นี่คือสิ่งที่ฉันต้องการฮ่าฮ่า
giantqtipz

1

ในบางกรณีคุณอาจใช้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วิธีการนี้อาจทำให้เกิดข้อผิดพลาด 'ความลึกของการอัปเดตสูงสุดเกิน'


0

ด้วย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);


0

ฉันเพิ่งจัดการกับปัญหานี้ดังนั้นฉันจะเพิ่มวิธีแก้ปัญหาของฉันเป็นส่วนเสริมสำหรับคำตอบอื่น ๆ ที่ได้รับ

ปัญหาคือมันuseEffectไม่ได้ผลจริงอย่างที่คุณต้องการเนื่องจากการโทรจะถูกทริกเกอร์หลังจากการเรนเดอร์ครั้งแรกเท่านั้นจึงเกิดความล่าช้าที่ไม่ต้องการ
หากคุณใช้ตัวจัดการสถานะบางอย่างเช่น redux โอกาสที่คุณจะได้รับการกะพริบบนหน้าจอเนื่องจากสถานะค้างอยู่ในร้านค้า

สิ่งที่คุณต้องการจริงๆคือใช้useLayoutEffectเนื่องจากสิ่งนี้ถูกทริกเกอร์ทันที

ดังนั้นฉันจึงเขียนฟังก์ชันยูทิลิตี้ขนาดเล็กที่ฉันใส่ไว้ในไดเร็กทอรีเดียวกับเราเตอร์ของฉัน:

export const callApis = (fn, path) => {
    useLayoutEffect(() => {
      fn();
    }, [path]);
};

ซึ่งฉันเรียกจากภายในส่วนประกอบ HOC ดังนี้:

callApis(() => getTopicById({topicId}), path);

pathเป็นเสาที่ได้รับการผ่านในวัตถุเมื่อใช้matchwithRouter

ฉันไม่ชอบฟัง / ไม่ฟังประวัติด้วยตนเองจริงๆ นั่นเป็นเพียง imo

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