反応ルーターv4でルート変更をリッスンする方法は?


回答:


170

withRouterlocation小道具を手に入れるのに使います。新しいルートのためにコンポーネントが更新されたら、値が変更されたかどうかを確認します。

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

それが役に立てば幸い


21
反応ルーターv4で「this.props.location.pathname」を使用します。
ptorsson 2017年

4
@ledfusion私は同じことをして使用しwithRouterていますが、エラーが発生していますYou should not use <Route> or withRouter() outside a <Router><Router/>上記のコードにはコンポーネントがありません。それで、それはどのように機能していますか?
異端者

1
@maverickさん、こんにちは。コードがどのように見えるかはわかりませんが、上記の例では、<Switch>コンポーネントは事実上のルーターとして機能しています。<Route>一致するパスを持つ最初のエントリのみがレンダリングされます。<Router/>このシナリオではコンポーネントは必要ありません
ブリックポップ

1
@withRouterを使用するには、npm install --save-dev transform-decorators-legacyをインストールする必要があります
Sigex

68

上記を拡張するには、履歴オブジェクトを取得する必要があります。を使用している場合は、コンポーネントをBrowserRouterインポートwithRouterして高次コンポーネント(HoC)でラップし、propsを介して履歴オブジェクトのプロパティと関数にアクセスできます。

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

知っておくべき唯一のことは、withRouterや他のほとんどの方法でhistoryオブジェクトにアクセスして、オブジェクトを構造化解除してプロップを汚染するように見えることです。


答えは私が質問に関係なく何かを理解するのに役立ちました:)。しかし、修正withRoutesしてくださいwithRouter
Sergey Reutskiy、2017

うん、申し訳ありませんが、指摘してくれてありがとう。投稿を修正しました。質問の先頭に正しいインポートを配置してから、コード例でスペルを間違えました。
Sam Parmenter 2017

5
変数ではなく、withRouter現在のバージョンが渡されると思いhistoryますlisten
マイクブリッジ2017

5
リスニングを解除することを示すために投稿を修正するのは良いことです。このコードにはメモリリークがあります。
AndrewSouthpaw

34

履歴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イベントをトリガーしないので、これだけではすべてのルート変更をカバーできません。
ライアン

1
@ライアンそれhistory.pushはトリガーを行うようですhistory.listen履歴v4ドキュメントのベースURL使用ご覧ください。これは実際にはブラウザのネイティブオブジェクトのラッパーであるため、ネイティブオブジェクトとまったく同じようには動作しません。historyhistory
Rockallite、

これは、コンポーネントのライフサイクルイベントへの対応とは関係のないイベントプッシュのためにルートの変更をリッスンする必要がある場合が多いため、より良いソリューションのように感じられます。
Daniel Dubovski、2018

12
潜在的なメモリリーク!これを行うことは非常に重要です!「あなたはhistory.listenを使用してリスナーを接続すると、それはその後、クリーンアップロジックで呼び出すことができ、リスナーを削除するために使用できる機能を返しますconst unlisten = history.listen(myListener); unlisten();
DehanデCroos

履歴パッケージのドキュメントについては、こちらをご覧ください。github.com/ReactTraining/history/blob/master/docs/...
ジェイソン・キム

26

withRouterhistory.listen、およびuseEffect(フックを反応させるのは)一緒に非常にうまく動作します:

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ます。戻り値は、でうまく機能するシャットダウンハンドラーです。


13

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

4
エラーで問題が発生したときのメモ:Cannot read property 'location' of undefined at useLocation。ルータをツリーに配置するコンポーネントと同じコンポーネントにuseLocation()コールが含まれていないことを確認する必要があります。こちらを参照してください
toddg

11

フック付き:

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>コンポーネントとしてインポートしてレンダリングする


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

}

フック付き


ホリージェシス!それはどのように機能しますか?あなたの答えはクールです!デバッガーポイントをuseEffectに配置しましたが、パス名を変更したときはいつでも、場所が未定義のままでした。あなたは何か良い記事を共有できますか?明確な情報を見つけるのが難しいため
AlexNikonov

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

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

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

  return null;
}

ハッシュの変更も監視しますか?route / a#1-> route / a#2
Naren

1

反応フックで、私は使用しています 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

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

pathmatchを使用するときにオブジェクトに渡される小道具ですwithRouter

私は実際に手動でヒストリを聞いたり聞いたりすることを好みません。それはただのイモです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.