Phát hiện thay đổi tuyến đường với bộ định tuyến phản ứng


87

Tôi phải triển khai một số logic nghiệp vụ tùy thuộc vào lịch sử duyệt web.

Những gì tôi muốn làm là một cái gì đó như thế này:

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

Có cách nào để nhận cuộc gọi lại từ bộ định tuyến phản ứng khi URL được cập nhật không?


Bạn đang sử dụng phiên bản react-router nào? Điều đó sẽ xác định cách tiếp cận tốt nhất. Tôi sẽ cung cấp câu trả lời khi bạn cập nhật. Điều đó đang được nói, withRouter HoC có lẽ là đặt cược tốt nhất của bạn để xác định vị trí thành phần. Nó sẽ cập nhật thành phần của bạn với ({trận đấu, lịch sử và vị trí}) mới bất cứ khi nào một tuyến đường thay đổi. Bằng cách này, bạn không cần phải đăng ký và hủy đăng ký các sự kiện theo cách thủ công. Có nghĩa là nó dễ sử dụng với các thành phần không trạng thái chức năng cũng như các thành phần lớp.
Kyle Richardson,

Câu trả lời:


115

Bạn có thể sử dụng history.listen()chức năng khi cố gắng phát hiện thay đổi tuyến đường. Xem xét bạn đang sử dụng react-router v4, bọc thành phần của bạn bằng withRouterHOC để có quyền truy cập vào phần historyhỗ trợ.

history.listen()trả về một unlistenhàm. Bạn sẽ sử dụng điều này để unregisternghe.

Bạn có thể định cấu hình các tuyến đường của mình như

index.js

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

và sau đó trong 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);

Từ tài liệu lịch sử :

Bạn có thể nghe các thay đổi đối với vị trí hiện tại bằng cách sử dụng 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}`)
})

Đối tượng vị trí triển khai một tập hợp con của giao diện window.location, bao gồm:

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

Vị trí cũng có thể có các thuộc tính sau:

location.state - Một số trạng thái bổ sung cho vị trí này không nằm trong URL (được hỗ trợ trong createBrowserHistorycreateMemoryHistory)

location.key- Một chuỗi duy nhất đại diện cho vị trí này (được hỗ trợ trong createBrowserHistorycreateMemoryHistory)

Hành động là một trong những PUSH, REPLACE, or POPtùy thuộc vào cách người dùng truy cập vào URL hiện tại.

Khi bạn đang sử dụng phản ứng-router v3 bạn có thể tận dụng history.listen()từ historygói như đã đề cập ở trên hoặc bạn cũng có thể tận dụngbrowserHistory.listen()

Bạn có thể định cấu hình và sử dụng các tuyến đường của mình như

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

anh ấy sử dụng v3 và câu thứ hai của câu trả lời của bạn nói " Xét bạn đang sử dụngreact-router v4 "
Kyle Richardson

1
@KyleRichardson Tôi nghĩ bạn lại hiểu lầm tôi, tôi chắc chắn phải làm việc với tiếng Anh của mình. Tôi có nghĩa rằng nếu bạn đang sử dụng phản ứng-router v4 và bạn đang sử dụng đối tượng lịch sử thì bạn cần phải quấn thành phần của bạn vớiwithRouter
Shubham Khatri

@KyleRichardson Tôi bạn thấy câu trả lời đầy đủ của tôi, tôi cũng đã thêm cách để làm điều đó trong v3. Một điều nữa, OP nhận xét rằng ông đang sử dụng v3 hôm nay và tôi đã thưa: về vấn đề ngày hôm qua
Shubham Khatri

1
@ShubhamKhatri Có nhưng cách đọc câu trả lời của bạn sai. Anh ấy không sử dụng v4 ... Ngoài ra, tại sao bạn sẽ sử dụng history.listen()khi sử dụng withRouterđã cập nhật thành phần của mình với các đạo cụ mới mỗi khi định tuyến xảy ra? Bạn có thể làm một so sánh đơn giản nextProps.location.href === this.props.location.hreftrong componentWillUpdatethực hiện bất cứ điều gì bạn cần phải làm gì nếu nó đã thay đổi.
Kyle Richardson

1
@Aris, bạn có nhận thay đổi để thử không
Shubham Khatri

36

Cập nhật cho 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

Nếu bạn muốn lắng nghe historyđối tượng trên toàn cầu, bạn sẽ phải tự tạo nó và chuyển nó cho Router. Sau đó, bạn có thể nghe nó bằng listen()phương pháp của nó :

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

Thậm chí tốt hơn nếu bạn tạo đối tượng lịch sử dưới dạng một mô-đun, vì vậy bạn có thể dễ dàng nhập nó vào bất cứ đâu bạn có thể cần (ví dụ: import history from './history';


9

react-router v6

Trong phiên bản v6 sắp tới , điều này có thể được thực hiện bằng cách kết hợp các móc useLocationuseEffect

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

Để sử dụng lại thuận tiện, bạn có thể làm điều này trong một useLocationChangemóc tùy chỉnh

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

Nếu bạn cũng cần xem tuyến đường trước đó thay đổi, bạn có thể kết hợp với một usePrevioushook

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

Điều quan trọng cần lưu ý là tất cả các kích hoạt ở trên trên tuyến khách hàng đầu tiên đang được gắn kết, cũng như các thay đổi tiếp theo. Nếu đó là sự cố, hãy sử dụng ví dụ sau và kiểm tra xem có prevLocationtồn tại hay không trước khi làm bất cứ điều gì.


Tôi có một câu hỏi. Nếu một số thành phần đã được hiển thị và tất cả chúng đều đang xem useLocation thì tất cả các useEffects của chúng sẽ được kích hoạt. Làm cách nào để xác minh rằng vị trí này là chính xác cho thành phần cụ thể sẽ được hiển thị?
Kex

1
Xin chào @Kex - chỉ cần làm rõ locationở đây là vị trí trình duyệt, vì vậy nó giống nhau ở mọi thành phần và luôn đúng theo nghĩa đó. Nếu bạn sử dụng hook trong các thành phần khác nhau, tất cả chúng sẽ nhận được các giá trị giống nhau khi vị trí thay đổi. Tôi đoán những gì họ làm với thông tin đó sẽ khác nhau, nhưng nó luôn nhất quán.
davnicwil

Điều đó có lý. Chỉ cần tự hỏi làm thế nào một thành phần sẽ biết liệu thay đổi vị trí có liên quan đến việc chính nó thực hiện một hành động hay không. Ví dụ: một thành phần nhận được bảng điều khiển / danh sách nhưng làm thế nào nó biết nếu nó được gắn với vị trí đó hay không?
Kex

Trừ khi tôi làm điều gì đó như if (location.pathName === “dashboard / list”) {..... hành động}. Tuy nhiên, nó có vẻ không phải là đường dẫn mã hóa cứng cho một thành phần cho lắm.
Kex

8

Đây là một câu hỏi cũ và tôi không hiểu rõ nhu cầu kinh doanh của việc lắng nghe các thay đổi về lộ trình để thúc đẩy thay đổi lộ trình; có vẻ vòng vo.

NHƯNG nếu bạn đã kết thúc ở đây vì tất cả những gì bạn muốn là cập nhật 'page_path'thay đổi trên tuyến đường phản ứng cho google analytics / thẻ trang web toàn cầu / một cái gì đó tương tự, đây là một móc nối bạn hiện có thể sử dụng. Tôi đã viết nó dựa trên câu trả lời được chấp nhận:

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

Bạn nên sử dụng hook này một lần trong ứng dụng của mình, ở đâu đó gần đầu nhưng vẫn bên trong bộ định tuyến. Tôi có nó trên một App.jscái trông như thế này:

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

Tôi gặp câu hỏi này khi tôi đang cố gắng tập trung trình đọc màn hình ChromeVox lên đầu "màn hình" sau khi điều hướng đến màn hình mới trong ứng dụng một trang React. Về cơ bản, cố gắng mô phỏng điều gì sẽ xảy ra nếu trang này được tải bằng cách nhấp vào liên kết đến một trang web mới do máy chủ hiển thị.

Giải pháp này không yêu cầu bất kỳ trình nghe nào, nó sử dụng withRouter()componentDidUpdate()phương pháp vòng đời để kích hoạt một lần nhấp nhằm tập trung ChromeVox vào phần tử mong muốn khi điều hướng đến đường dẫn url mới.


Thực hiện

Tôi đã tạo một thành phần "Màn hình" được bao quanh thẻ chuyển đổi bộ định tuyến phản ứng chứa tất cả các màn hình ứng dụng.

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

Screen.tsx Thành phần

Lưu ý: Thành phần này sử dụng 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)

Tôi đã thử sử dụng focus()thay vì click(), nhưng nhấp chuột khiến ChromeVox ngừng đọc bất kỳ nội dung nào nó hiện đang đọc và bắt đầu lại khi tôi yêu cầu nó bắt đầu.

Lưu ý nâng cao: Trong giải pháp này, điều hướng <nav>bên trong thành phần Màn hình và hiển thị sau khi <main>nội dung được định vị trực quan phía trên mainsử dụng css order: -1;. Vì vậy, trong mã giả:

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

Nếu bạn có bất kỳ suy nghĩ, nhận xét hoặc mẹo nào về giải pháp này, vui lòng thêm nhận xét.


1

Bộ định tuyến React V5

Nếu bạn muốn tên đường dẫn dưới dạng chuỗi ('/' hoặc 'người dùng'), bạn có thể sử dụng như sau:

  // 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])
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.