反応ルーターV4を使用してプログラムでナビゲートする


335

react-routerv3からv4に置き換えたところです。
しかし、のメンバー関数をプログラムでナビゲートする方法がわかりませんComponent。つまり、handleClick()関数では、/path/some/whereいくつかのデータを処理した後に移動したいと思います。以前は次の方法でそれを行っていました。

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')

しかし、v4ではそのようなインターフェースを見つけることができません。
v4を使用してナビゲートするにはどうすればよいですか?


3
小道具を通じてv4の履歴オブジェクトにアクセスできます:this.props.history.push( '/')
colemerrick

6
とは別の場所からアクセスしたい場合はどうなりますComponentか?たとえば、reduxアクションの内部。
Maximus S

73
あるリンクから別のリンクに移動するのがなぜ複雑なのかと思います=))
Thai Tran

12
この時代では、リダイレクトはそれほど複雑ではないと思います。
JohnAllen 2017

この投稿は役に立ちました:medium.com/@anneeb/redirecting-in-react-4de5e517354a
BioData41

回答:


413

ブラウザ環境をターゲットにしている場合はreact-router-dom、ではなくパッケージを使用する必要がありますreact-router。これらは、コア(react)とプラットフォーム固有のコード(react-domreact-native)を分離するためにReactが行ったのと同じアプローチに従っており、2つの別個のパッケージをインストールする必要がないため、環境パッケージにはすべてが含まれていますあなたが必要です。次のようにプロジェクトに追加できます。

yarn add react-router-dom

または

npm i react-router-dom

最初に行う必要があるのは<BrowserRouter>、アプリケーションの最上位の親コンポーネントとしてを提供することです。<BrowserRouter>はHTML5 historyAPIを使用して管理します。そのため、自分でインスタンス化して<BrowserRouter>コンポーネントに渡すことを心配する必要はありません(以前のバージョンで必要でした)。

V4では、プログラムでナビゲートするには、アプリケーションの最上位の親としてプロバイダーコンポーネントがある限り、historyReactを介して利用できるオブジェクトにアクセスする必要があります。ライブラリは、それ自体がプロパティとして含むオブジェクトをコンテキストを通じて公開します。インタフェースは、次のようないくつかのナビゲーションメソッドを提供しています、そしてとりわけ、。プロパティとメソッドのリスト全体をここで確認できますcontext<BrowserRouter> routerhistoryhistorypushreplacegoBack

Redux / Mobxユーザーへの重要な注意

アプリケーションの状態管理ライブラリとしてreduxまたはmobxを使用している場合、ロケーションを認識する必要があるが、URL更新のトリガー後に再レンダリングされないコンポーネントに問題が発生した可能性があります

これは、コンテキストモデルを使用してコンポーネントにreact-router渡さlocationれるために起こります。

接続とオブザーバーはどちらも、shouldComponentUpdateメソッドが現在の小道具と次の小道具の浅い比較を行うコンポーネントを作成します。これらのコンポーネントは、少なくとも1つのプロップが変更された場合にのみ再レンダリングされます。つまり、場所が変更されたときに確実に更新されるようにするには、場所が変更されたときに変更される小道具を提供する必要があります。

これを解決するための2つのアプローチは次のとおりです。

  • 接続されたコンポーネントをパスレスでラップします<Route />。現在のlocationオブジェクトは<Route>、レンダリングするコンポーネントに渡されるプロップの1つです。
  • 接続されたコンポーネントをwithRouter高次コンポーネントでラップします。これは、実際には同じ効果を持ちlocation、プロップとして注入されます

それを別にして、プログラムでナビゲートするには、推奨順に並べられた4つの方法があります。

1.- <Route>コンポーネントの使用

宣言的なスタイルを促進します。v4より前は、<Route />コンポーネントはコンポーネント階層の最上位に配置されていたため、事前にルート構造を考える必要がありました。ただし、ツリー内の任意の場所<Route>コンポーネントを配置できるようになり、URLに応じて条件付きでレンダリングするためのより細かい制御が可能になります。注入、および、コンポーネントの小道具として。(例えば、ナビゲーションメソッドは、、...)の特性として利用可能であるオブジェクト。RoutematchlocationhistorypushreplacegoBackhistory

で何かをレンダリングするための3つの方法がありますRouteいずれかを使用することにより、componentrenderまたはchildren小道具が、同じ以上のものを使用していないがRoute。選択はユースケースによって異なりますが、基本的に最初の2つのオプションはpath、がURLの場所と一致する場合にのみコンポーネントをレンダリングしますがchildren、コンポーネントでは、パスが場所と一致するかどうかにかかわらずレンダリングされます(URLに基​​づいてUIを調整するのに役立ちます)マッチング)。

コンポーネントのレンダリング出力をカスタマイズする場合は、コンポーネントを関数にラップしてrenderオプションを使用する必要があります。これによりmatchlocationおよび以外の必要な他の小道具をコンポーネントに渡すことができますhistory。説明する例:

import { BrowserRouter as Router } from 'react-router-dom'

const ButtonToNavigate = ({ title, history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    {title}
  </button>
);

const SomeComponent = () => (
  <Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)    

const App = () => (
  <Router>
    <SomeComponent /> // Notice how in v4 we can have any other component interleaved
    <AnotherComponent />
  </Router>
);

2.-使用withRouterHOC

この高次コンポーネントは、と同じ小道具を注入しRouteます。ただし、ファイルごとに1つのHoCしか持てないという制限があります。

import { withRouter } from 'react-router-dom'

const ButtonToNavigate = ({ history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    Navigate
  </button>
);


ButtonToNavigate.propTypes = {
  history: React.PropTypes.shape({
    push: React.PropTypes.func.isRequired,
  }),
};

export default withRouter(ButtonToNavigate);

3.- Redirectコンポーネントの使用

をレンダリングする<Redirect>と、新しい場所に移動します。ただし、デフォルトでは、サーバー側のリダイレクト(HTTP 3xx)のように、現在の場所が新しい場所に置き換えられることに注意してください。新しい場所はtoprop によって提供され、文字列(リダイレクト先のURL)またはlocationオブジェクトにすることができます。代わりに新しいエントリを履歴プッシュする場合は、pushプロップも渡して、次のように設定しますtrue

<Redirect to="/your-new-location" push />

4.- routerコンテキストから手動でアクセスする

コンテキストはまだ実験的なAPIであり、Reactの将来のリリースで破損/変更される可能性があるため、やや落胆

const ButtonToNavigate = (props, context) => (
  <button
    type="button"
    onClick={() => context.router.history.push('/my-new-location')}
  >
    Navigate to a new location
  </button>
);

ButtonToNavigate.contextTypes = {
  router: React.PropTypes.shape({
    history: React.PropTypes.object.isRequired,
  }),
};

言うまでもなく、メモリ内の<NativeRouter>ナビゲーションスタックを複製し、パッケージを通じて利用可能なReact Nativeプラットフォームをターゲットにするなど、非ブラウザーエコシステム向けのルーターコンポーネントもあります。react-router-native

詳細については、公式ドキュメントをご覧ください。ライブラリの共著者の1人が作成したビデオがあり、react-router v4のかなりクールな紹介を提供し、いくつかの主要な変更点を強調しています。


2
私はV4を使用していますが、上記は正常に動作します。いくつかの奇妙な選択肢があるようですが、上記は間違いなく機能するので、V4ルーターをいじくり回してかなりの時間を費やしました。私はあなたがwithRouterからインポートしていると思いますreact-router-dom
Sam Parmenter

3
withRouterからインポートしていreact-router-domます。history.pushはURLを変更しますが<Route>、ページを強制的に更新するまではロードされないようです...
BreakDS

1
@rauliyohmc私はこれについて間違っていました。問題は、で<Router> 装飾されたReactコンポーネントの内部にあるため、この問題@observerが発生することです。回避@withRouter策は、そのようなすべてのReactコンポーネントを使用することです。
BreakDS 2017年

3
この素晴らしい答えに出くわした人にとってwithRouterは、単に内部でHOCを使用するだけRouteです。つまり、履歴、マッチ、場所の3つの小道具のみを使用します。上記の例でpushは、withRouterがに追加される小道具であるように見えますButtonToNavigateが、そうではありません。props.history.push代わりに使用する必要があります。これが少し混乱した他の人を助けることを願っています。
vinnymac 2017年

39
ワオ。browserHistory.push('/path/some/where')はるかに単純なようです。作者は命令型プログラミングを阻止しようとしていますが、時にはそれだけでうまく機能します!
lux

149

それを成し遂げる最も簡単な方法:

this.props.history.push("/new/url")

注意:

  • history propfrom親コンポーネントを、アクションが使用できない場合にアクションを呼び出すコンポーネントに渡すことができます。

10
4.0ルーターを使用していますが、小道具に履歴キーがありません。どうすれば修正できますか?
Nicolas S.Xu

41
this.props.historyコンポーネントで使用できない場合はimport {withRouter} from 'react-router-dom'、それから export default withRouter(MyComponent)(またはconst MyComponent = withRouter(...))を使用してhistory、コンポーネントの小道具にアイテムを挿入します。
Malvineous、2018

@Malvineous thats興味深い、これについて知らなかった!やってみよう!
ジックホセ

2
これは私にとって非常に効果的であり、すべての回答にそのような長い説明が必要な理由がわかりません。
ジョシュアピンター

コンポーネントの再マウントを防止しhistory.push、クリックしたときのように更新をトリガーする方法<Link>
Rahul Yadav

55

React-Router v4に移行するときにも同様の問題が発生したため、以下の解決策を説明します。

この答えを問題を解決する正しい方法とは見なさないでください。ReactRouter v4がより成熟し、ベータ版を残すようになると、より良いものが発生する可能性が高いと思います(すでに存在している可能性があり、まだ発見していません)。 。

コンテキストについてはRedux-Saga、プログラムを使用して履歴オブジェクトを変更することがある(たとえば、ユーザーが正常に認証された場合など)ため、この問題が発生しました。

React Routerのドキュメントで<Router> コンポーネントを確認すると、プロップを介して独自の履歴オブジェクトを渡すことができることがわかります。これは、ソリューションの本質である- 私たちは歴史のオブジェクトを提供するReact-Routerからグローバルモジュール。

手順:

  1. 履歴npmモジュールをインストールする- yarn add history または npm install history --save
  2. ファイルを作成history.js自分の中でApp.js上位のフォルダを(これは私の好みでした)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
  3. この履歴オブジェクトをルーターコンポーネントに追加します。

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
  4. グローバル履歴オブジェクトをインポートして前と同じようにURLを調整します。

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');

すべてが同期されているはずです。また、コンポーネントやコンテナを介さずに、プログラムで履歴オブジェクトを設定する方法にもアクセスできます。


5
この変更は私にとってうまくいきましたが、それは私がコンポーネントの外をナビゲートしているからです。OPのようなコンポーネントの内部をナビゲートしている場合、@ rauliyohmcによって提案されたアプローチを使用し、Routeコンポーネントから渡された小道具を使用します。
Dan Ellis

2
これは08/17の推奨アプローチです
スペッツ

2
@Spets私の場合、この種のアプローチを使用すると、リンクはプッシュ後に適切に更新されますが、コンポーネントは適切にレンダリングされません(たとえば、リンクを更新した後、ページを強制的に更新しない限り、コンポーネントは更新されません)。これが推奨されるアプローチであることをどこで見つけましたか?リンク/ソースはありますか?
涼しい

2
@ScottCoates上記の例を使用して、実際には履歴をパラメーターとして提供することでソートしましたが、その後、自分でノードモジュールのデバッグを行いました。最新バージョンのreact-router-domにルーターと呼ばれる別のオブジェクトが存在する場合、 "BrowserHistory as Router"のインポートを使用して、ウェブ全体でよくある間違いがあります。これを上記の例で作成した履歴と組み合わせて使用​​すると、問題なく動作します。
クール

2
URLは更新されますが、ページは新しいルートに基づいてレンダリングされません。解決策はありますか?なぜトリガールートはとても難しいのですか?リアクションをデザインする人は頭の中にないのですか?
Nicolas S.Xu

43

TL; DR:

if (navigate) {
  return <Redirect to="/" push={true} />
}

シンプルで宣言的な答えは、以下と<Redirect to={URL} push={boolean} />組み合わせて使用する必要があるということです。setState()

push:boolean- trueの場合、リダイレクトすると、現在のエントリを置き換えるのではなく、新しいエントリが履歴にプッシュされます。


import { Redirect } from 'react-router'

class FooBar extends React.Component {
  state = {
    navigate: false
  }

  render() {
    const { navigate } = this.state

    // here is the important part
    if (navigate) {
      return <Redirect to="/" push={true} />
    }
   // ^^^^^^^^^^^^^^^^^^^^^^^

    return (
      <div>
        <button onClick={() => this.setState({ navigate: true })}>
          Home
        </button>
      </div>
    )
  }
}

ここに完全な例があります。詳細はこちら

PS。この例では、ES7 +プロパティ初期化子を使用して状態を初期しています。興味があればこちらご覧ください。


5
これは受け入れられるべき答えです。最もシンプルでエレガントなソリューション!@lustoykovの+1
Ali Abbas Jaffri

1
私のボタンはヘッダーにあり、それ以外の場合は1回だけナビゲートするため、componentDidUpdateでナビゲートをfalseに戻しました。
peterorum 2017

これは、ページの読み込み時のリダイレクトについて知っている場合にのみ機能します。非同期の呼び出しが返されるのを待っている場合(つまり、Googleなどでの認証)、history.push()メソッドの1つを使用する必要があります。
brittohalloran

実際はそうではありませんが、<redirect />コンポーネントと組み合わせて、reactの宣言的な性質を活用できます。ページが読み込まれない場合は、別の<Redirect />にフォールバックできます
Lyubomir

@brittohalloran適切な反応ルーターを使用したこのメソッドのアイデアは、setStateを使用して再レンダリングを強制することです。
特別な誰か

15

useHistory関数コンポーネントを使用している場合はフックを使用します

useHistoryフックを使用してhistoryインスタンスを取得できます。

import { useHistory } from "react-router-dom";

const MyComponent = () => {
  var history = useHistory();

  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}

useHistoryフックは、あなたがナビゲートするために使用することが歴史のインスタンスにアクセスできます。

historyページコンポーネント内でプロパティを使用する

React Routerはhistory、ページコンポーネントを含むいくつかのプロパティを注入します。

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

子コンポーネントwithRouterをラップしてルーターのプロパティを挿入する

withRouterラッパーは、コンポーネントにルーターのプロパティを挿入します。たとえば、このラッパーを使用して、ユーザーメニュー内に配置されたログアウトボタンコンポーネントにルーターを挿入できます。

import { withRouter } from "react-router";

const LogoutButton = withRouter(({ history }) => {
  return (
    <button onClick={() => history.push("/login")}>
      Logout
    </button>
  );
});

export default LogoutButton;

11

propsを使用して履歴オブジェクトにアクセスすることもできます。 this.props.history.push('new_url')


5
コンポーネントがルーターの直下にある場合にのみ役立ちます。ないように、あなたはあなたがこの機能を必要とするすべての単一のコンポーネントに履歴小道具を渡す。
mwieczorek

3
this.props.historyコンポーネントで使用できない場合はimport {withRouter} from 'react-router-dom'、それから export default withRouter(MyComponent)(またはconst MyComponent = withRouter(...))を使用してhistory、コンポーネントの小道具にアイテムを挿入します。
Malvineous、2018

9

ステップ1:上にインポートするのは1つだけです。

import {Route} from 'react-router-dom';

ステップ2:ルートで、履歴を渡します。

<Route
  exact
  path='/posts/add'
  render={({history}) => (
    <PostAdd history={history} />
  )}
/>

ステップ3:履歴は次のコンポーネントの小道具の一部として受け入れられるため、簡単に次のことができます。

this.props.history.push('/');

それは簡単で本当に強力でした。


7

これは機能します:

import { withRouter } from 'react-router-dom';

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;

5

私の答えはアレックスのと似ています。React-Routerがこれを不必要に複雑にした理由はわかりません。本質的にグローバルなものにアクセスするためだけに、コンポーネントをHoCでラップする必要があるのはなぜですか?

とにかく、それらがどのように実装されているかを見てみると<BrowserRouter>、それは単なる歴史の小さなラッパーにすぎません。

その履歴を少し引き出して、どこからでもインポートできるようにします。ただし、トリックは、サーバー側のレンダリングを行っているimportときにhistoryモジュールを使用しようとすると、ブラウザーのみのAPIを使用するため機能しません。ただし、通常はクリックまたはその他のクライアント側イベントに応答してリダイレクトするだけなので、問題ありません。したがって、それを偽ることはおそらく問題ありません:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}

webpackの助けを借りて、いくつかの変数を定義して、現在の環境を知ることができます。

plugins: [
    new DefinePlugin({
        '__SERVER__': 'false',
        '__BROWSER__': 'true', // you really only need one of these, but I like to have both
    }),

そして今、あなたはできる

import history from './history';

どこからでも。サーバー上で空のモジュールを返すだけです。

これらのマジック変数を使用したくない場合requireは、必要な場所(イベントハンドラー内)でグローバルオブジェクトを使用する必要があります。importトップレベルでしか機能しないため、機能しません。


6
いまいましい彼らはそれをとても複雑にした。
アビシェク2017年

4
私はあなたに完全に同意しました。これはナビゲーションだけでは非常に複雑です
Thai Tran

5

@rgommezzはほとんどのケースをカバーしていると思います。

// history is already a dependency or React Router, but if don't have it then try npm install save-dev history

import createHistory from "history/createBrowserHistory"

// in your function then call add the below 
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");

これにより、コンポーネントで多くのHoCを実行せずに、必要なコンポーネントからナビゲーションを実行するために呼び出すことができるアクション/呼び出しを使用した単純なサービスを作成できます...

なぜ誰もこのソリューションを以前に提供したことがない理由は明らかではありません。お役に立てば幸いです。何か問題がありましたらお知らせください。


4

私は数日間v4をテストしてきましたが、今のところそれを気に入っています!しばらくすると、それは理にかなっています。

私も同じ質問があり、次のように処理するのが最も効果的であることがわかりました(意図したとおりである可能性もあります)。状態、三項演算子、およびを使用し<Redirect>ます。

コンストラクタで()

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);

render()内

render(){
    return (
        <div>
        { this.state.redirectTo ?
            <Redirect to={{ pathname: this.state.redirectTo }} /> : 
            (
             <div>
               ..
             <button onClick={ this.clickhandler } />
              ..
             </div>
             )
         }

clickhandler()内

 this.setState({ redirectTo: '/path/some/where' });

それが役に立てば幸い。お知らせ下さい。


この方法には落とし穴がありますか?私のプロジェクトでは、コンストラクターで状態を設定した場合にのみ機能し、そこから任意のページにリダイレクトできます。しかし、イベントに状態を設定すると(たとえば、小道具は変更されました)、新しい状態で呼び出されたrenderメソッドが表示されますが、リダイレクトが発生しません。同じページが表示されます
Dmitry Malugin

落とし穴は、履歴を置き換えるため、元に戻すことはできません-基本的に、これは良い解決策ではありません。
dannytenaglias 2017

4

ReactJSはWebアプリケーションを作成するためのまったく異なる方法であるため、私はこれをしばらくの間苦労しました。

混乱を取り除くために、別のコンポーネントを作成しました。

// LinkButton.js

import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';

export default class LinkButton extends React.Component {

    render() {
        return (
            <Route render={({history}) => (
                <button {...this.props}
                       onClick={() => {
                           history.push(this.props.to)
                       }}>
                    {this.props.children}
                </button>
            )}/>
        );
    }
}

LinkButton.propTypes = {
    to: PropTypes.string.isRequired
};

次に、それをrender()メソッドに追加します。

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>

このソリューションは非常に便利です。コードをコピーしています。あなたがそれをgithubに置いたことを私に知らせてください-私はそれをあなたに直接帰属させます。
Abhinav Manchanda

3

この恐ろしい設計に対処する方法は他にないため、withRouter HOCアプローチを使用する汎用コンポーネントを作成しました。以下の例ではbutton要素をラップしていますが、必要なクリック可能な要素に変更できます。

import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';

const NavButton = (props) => (
  <Button onClick={() => props.history.push(props.to)}>
    {props.children}
  </Button>
);

NavButton.propTypes = {
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }),
  to: PropTypes.string.isRequired
};

export default withRouter(NavButton);

使用法:

<NavButton to="/somewhere">Click me</NavButton>

3
this.props.history.push("/url")

this.props.historyがコンポーネントで使用できない場合は、これを試してください

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  

本当にありがとう!
QauseenMZ

1

ルートをアプリケーションで切り替えてからボタンで切り替えたい場合があるので、これは私にとっては機能する最小限の実用的な例です。

import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'

class App extends Component {
  constructor(props) {
    super(props)

    /** @type BrowserRouter */
    this.router = undefined
  }

  async handleSignFormSubmit() {
    await magic()
    this.router.history.push('/')
  }

  render() {
    return (
      <Router ref={ el => this.router = el }>
        <Link to="/signin">Sign in</Link>
        <Route path="/signin" exact={true} render={() => (
          <SignPage onFormSubmit={ this.handleSignFormSubmit } />
        )} />
      </Router>
    )
  }
}

0

React Routerまたはを使用してルーターを完全に初期化する前にリダイレクトする必要がある場合React Router Domは、履歴オブジェクトにアクセスし、の構成内で新しい状態をプッシュするだけでリダイレクトを提供できますapp.js。以下を検討してください。

function getSubdomain(hostname) {
    let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
    let urlParts = regexParse.exec(hostname);
    return hostname.replace(urlParts[0], '').slice(0, -1);
}

class App extends Component {

    constructor(props) {
        super(props);


        this.state = {
            hostState: true
        };

        if (getSubdomain(window.location.hostname).length > 0) {
            this.state.hostState = false;
            window.history.pushState('', '', './login');
        } else {
            console.log(getSubdomain(window.location.hostname));
        }

    }


    render() {
        return (

            <BrowserRouter>
                {this.state.hostState ? (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                        <Route path="/" component={PublicContainer}/>
                    </div>
                ) : (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                    </div>
                )

                }
            </BrowserRouter>)
    }


}

ここでは、コンポーネントがレンダリングされる前に履歴オブジェクトと対話して、ルートをそのままにして効果的にリダイレクトできるようにして、サブドメインに依存する出力ルートを変更します。

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