web-dev-qa-db-ja.com

反応ルーターでルート変更を検出

閲覧履歴に応じていくつかのビジネスロジックを実装する必要があります。

私がやりたいのはこのようなものです:

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

URLが更新されたときにreact-routerからコールバックを受信する方法はありますか?

44
Aris

ルートの変更を検出しようとするときに history.listen() 関数を使用できます。 react-router v4を使用していることを考慮し、コンポーネントをwithRouter HOCでラップして、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);

履歴から docs

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でサポート)

アクションは、ユーザーが現在のURLにアクセスした方法に応じて、Push, REPLACE, or POPのいずれかです。

反応ルーターv3を使用している場合、上記のhistoryパッケージのhistory.listen()を使用するか、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>
        )
    }
} 
67
Shubham Khatri

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

10
Fabian Schultz

Reactシングルページアプリで新しい画面に移動した後、ChromeVoxスクリーンリーダーを「画面」の上部にフォーカスしようとしていたときにこの質問に出会いました。基本的に、新しいサーバーレンダリングされたWebページへのリンクをたどって、このページが読み込まれた場合に何が起こるかをエミュレートしようとしています。

このソリューションはリスナーを必要としません。新しいURLパスに移動するときに、withRouter()およびcomponentDidUpdate()ライフサイクルメソッドを使用してクリックをトリガーし、目的の要素にChromeVoxをフォーカスします。


実装

すべてのアプリ画面を含む反応ルータースイッチタグを囲む「スクリーン」コンポーネントを作成しました。

<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は現在読んでいるものの読み取りを停止し、開始するように指示した場所から再開します。

高度な注意:このソリューションでは、Screenコンポーネント内で<nav>コンテンツの後にレンダリングされるナビゲーション<main>は、css order: -1;を使用してmainの上に視覚的に配置されます。擬似コードで:

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

このソリューションに関するご意見、コメント、またはヒントがある場合は、コメントを追加してください。

1
Beau Smith