Làm thế nào để lắng nghe thay đổi tuyến đường trong bộ định tuyến v4 phản ứng?


137

Tôi có một vài nút hoạt động như các tuyến đường. Mỗi khi tuyến đường được thay đổi, tôi muốn đảm bảo nút đang hoạt động thay đổi.

Có cách nào để lắng nghe những thay đổi tuyến đường trong phản ứng của bộ định tuyến v4 không?


Câu trả lời:


170

Tôi sử dụng withRouterđể có được chỗ dựa location. Khi thành phần được cập nhật vì một tuyến mới, tôi kiểm tra xem giá trị có thay đổi không:

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

Hy vọng nó giúp


21
Sử dụng 'this.props.location.pathname' trong bộ định tuyến v4.
ptorson

4
@ledfusion Tôi đang làm tương tự và sử dụng withRouter, nhưng tôi đang gặp lỗi You should not use <Route> or withRouter() outside a <Router>. Tôi không thấy bất kỳ <Router/>thành phần nào trong đoạn mã trên. Vậy nó hoạt động như thế nào?
maverick

1
Xin chào @maverick. Tôi không chắc mã của bạn trông như thế nào, nhưng trong ví dụ trên, <Switch>thành phần này đóng vai trò là bộ định tuyến thực tế. Chỉ <Route>mục đầu tiên có đường dẫn phù hợp, sẽ được hiển thị. Không cần bất kỳ <Router/>thành phần nào trong kịch bản này
brickpop

1
để sử dụng @withRouter bạn cần cài đặt npm install --save-dev Transform-decorators-legacy
Sigex

67

Để mở rộng ở trên, bạn sẽ cần lấy tại đối tượng lịch sử. Nếu bạn đang sử dụng BrowserRouter, bạn có thể nhập withRoutervà bọc thành phần của mình bằng thành phần bậc cao hơn (HoC) để có quyền truy cập thông qua các đạo cụ đến các thuộc tính và chức năng của đối tượng lịch sử.

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

Điều duy nhất cần biết là vớiRouter và hầu hết các cách khác để truy cập vào history dường như làm ô nhiễm đạo cụ khi chúng cấu trúc lại vật thể vào đó.


Câu trả lời giúp tôi hiểu một cái gì đó bất kể câu hỏi :). Nhưng sửa withRoutessang withRouter.
Serge Reutskiy

Yep, xin lỗi, cảm ơn đã chỉ ra rằng. Tôi đã sửa đổi bài viết. Tôi đặt nhập chính xác ở đầu câu hỏi và sau đó viết sai chính tả trong ví dụ mã.
Sam Parmenter

5
Tôi nghĩ rằng phiên bản hiện tại của withRouter vượt qua historychứ không phải là biến listen.
mikebridge

5
Sẽ là tốt để sửa đổi bài viết để chứng minh không nghe; mã này có một bộ nhớ bị rò rỉ trong đó.
AndrewSouthpaw

34

Bạn nên sử dụng lib v4 lịch sử .

Ví dụ từ đó

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
Các lệnh history.pushState () và history.replaceState () không kích hoạt sự kiện popstate, do đó, điều này một mình sẽ không bao gồm tất cả các thay đổi tuyến đường.
Ryan

1
@Ryan Có vẻ như history.pushkhông kích hoạt history.listen. Xem ví dụ Sử dụng URL cơ sở trong tài liệu v4 lịch sử . Bởi vì đó historythực sự là một trình bao bọc của historyđối tượng gốc của trình duyệt, nên nó không hoạt động chính xác như đối tượng gốc.
Rockallite

Điều này cảm thấy giống như một giải pháp tốt hơn, vì thường thì bạn cần lắng nghe các thay đổi tuyến đường để đẩy sự kiện, không liên quan đến phản ứng các sự kiện vòng đời thành phần.
Daniel Dubovski

12
Rò rỉ bộ nhớ tiềm năng! Rất quan trọng là bạn làm điều này! "Khi bạn đính kèm một trình nghe bằng history.listen, nó sẽ trả về một hàm có thể được sử dụng để loại bỏ trình nghe, sau đó có thể được gọi trong logic dọn dẹp:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos 4/12/18

Tới đây để xem tài liệu về gói lịch sử. github.com/ReactTraining/history/blob/master/docs/ triệt
Jason Kim

25

withRouter, history.listenuseEffect(Móc phản ứng) hoạt động khá độc đáo với nhau:

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)

Cuộc gọi lại người nghe sẽ kích hoạt bất cứ khi nào một tuyến đường được thay đổi, và sự trở lại history.listenlà một trình xử lý tắt máy hoạt động tốt useEffect.


13

v5.1 giới thiệu móc hữu ích 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>
}

4
Chỉ cần một lưu ý là tôi đã gặp sự cố với một lỗi : Cannot read property 'location' of undefined at useLocation. Bạn cần đảm bảo rằng lệnh gọiLocation () không nằm trong cùng một thành phần đặt bộ định tuyến vào cây: xem tại đây
chập chững

11

Với móc:

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)

Nhập và kết xuất dưới dạng <DebugHistory>thành phần


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

}

với móc


Holly Jesys! làm thế nào nó hoạt động? Câu trả lời của bạn thật tuyệt! buti đặt điểm gỡ lỗi trong useEffect nhưng bất cứ khi nào tôi thay đổi tên đường dẫn, vị trí vẫn không xác định ? bạn có thể chia sẻ bất kỳ bài viết tốt? bởi vì thật khó để tìm thấy bất kỳ thông tin rõ ràng nào
AlexNikonov

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

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

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

  return null;
}

Nó cũng xem thay đổi băm? tuyến / a # 1 -> tuyến / a # 2
Naren

1

Với phản ứng Hook, tôi đang sử dụng 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])

0

Trong một số trường hợp, bạn có thể sử dụng renderthuộc tính thay vì component, theo cách này:

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

Lưu ý rằng nếu bạn thay đổi trạng thái trong onRouteChangephương thức, điều này có thể gây ra lỗi 'Độ sâu cập nhật tối đa vượt quá'.


0

Với useEffecthook, có thể phát hiện các thay đổi tuyến đường mà không cần thêm người nghe.

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

Tôi chỉ giải quyết vấn đề này, vì vậy tôi sẽ thêm giải pháp của mình dưới dạng bổ sung cho các câu trả lời khác được đưa ra.

Vấn đề ở đây là useEffectnó không thực sự hoạt động như bạn mong muốn, vì cuộc gọi chỉ được kích hoạt sau lần kết xuất đầu tiên nên có độ trễ không mong muốn.
Nếu bạn sử dụng một số trình quản lý trạng thái như redux, rất có thể bạn sẽ bị nhấp nháy trên màn hình vì trạng thái kéo dài trong cửa hàng.

Những gì bạn thực sự muốn là sử dụng useLayoutEffectvì điều này được kích hoạt ngay lập tức.

Vì vậy, tôi đã viết một chức năng tiện ích nhỏ mà tôi đặt trong cùng thư mục với bộ định tuyến của mình:

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

Mà tôi gọi từ bên trong thành phần HOC như thế này:

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

pathlà chỗ dựa được truyền vào matchđối tượng khi sử dụngwithRouter .

Tôi không thực sự ủng hộ việc nghe / không nghe theo cách thủ công trong lịch sử. Đó chỉ là imo.

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.