By Michael Sargent

ルーティングライブラリは、複雑な単一ページアプリケーションの重要なコンポーネントです。 ReactとReduxを使用してwebアプリを開発する場合は、おそらくReact Routerを使用したことがあるか、少なくとも聞いたことがあります。 これは、Reactのためのよく知られたルーティングライブラリであり、多くのユースケースのための優れた解決策です。

しかし、React RouterはReact/Reduxエコシステムの唯一の実行可能な解決策ではありません。 実際、ReactとReduxのために構築されたたくさんのルーティングソリューションがあり、それぞれが異なるApi、機能、目標を持っています—そしてリストは成長して 言うまでもなく、クライアント側のルーティングはすぐに消えることはなく、明日のルーティングライブラリにはまだ多くの設計スペースがあります。

今日、私はReduxのルーティングの主題にあなたの注意を持って来たいと思います。 Reduxをルーティングモデルのスターにするパラダイムであり、多くのReduxルーティングソリューションの共通のスレッドです。 Reactやその他のフロントエンドフレームワークで実際に使用するためのオプションを探る前に、フレームワークに依存しないコアAPIを100行以下のコードで組

ちょっと歴史

ブラウザでは、場所(URL情報)とセッション履歴(現在のブラウザタブによって訪問された場所のスタック)がグローバルwindowオブジェクトに格納されます。 彼らは経由でアクセス可能です:

  • window.location (ロケーションAPI)
  • window.history (履歴API)。

History APIは、ページのリロードを必要とせずにブラウザの履歴と場所を更新する機能のために注目すべき、次の履歴ナビゲーションメソッドを提供しています:

  • pushState(href) — 新しい場所を履歴スタックにプッシュします
  • replaceState(href) — スタック上の現在の場所を上書きします
  • back() — スタック上の前の場所に移動します
  • forward() — スタック上の次の場所に移動します
  • go(index) — スタック上のいずれかの方向の場所に移動します。

History ApiとLocation Apiを組み合わせることで、pushState routingと呼ばれる最新のクライアント側ルーティングパラダイムが可能になります。

今、historyのような近代的なラッパーライブラリに言及せずに、歴史と場所のApiに言及するのはほとんど犯罪です。

ReactTraining/history
セッション履歴を管理するJavaScriptgithub.com

historyは、異なるブラウザ実装間の矛盾をカバーしながら、ブラウザの履歴と場所とのインターフェイスのためのシンプルで強力なAPIを提供します。 これは、多くの近代的なルーティングライブラリでピアまたは内部依存関係として使用されており、この記事では複数の参照を行います。

ReduxとpushStateルーティング

私たちの物語の第二の主人公はReduxです。 それは2017年なので、私はあなたに紹介を惜しまず、ポイントに右に取得します:

reduxアプリケーションでプレーンなpushStateルーティングを使用することにより、アプ

ここでは、historyをインスタンス化してラップするReact Routerのように見えるものです:

history → React Router ↘ view Redux ↗

今、我々はすべてのデータがストア内に存在する必要がないことを知っています。 たとえば、ローカルコンポーネントの状態は、多くの場合、単一のコンポーネントに固有のデータを格納するのに適した場所です。

しかし、位置データは些細なことではありません。 これは、アプリケーションの状態の動的で重要な部分であり、ストアに属するデータの種類です。 ストアに保持することで、タイムトラベルデバッグのようなReduxの贅沢を可能にし、店舗に接続された任意のコンポーネントから簡単にアクセ

では、場所を店舗に移動するにはどうすればよいですか?

ブラウザがwindowに履歴と位置情報を読み込んで保存するという事実を回避することはできませんが、できることは、店舗内の位置データのコピーを保

それはreact-router-reduxがReact Routerのためにすることではありませんか?

はい、ただし、Redux DevToolsのタイムトラベル機能を有効にするためだけです。 アプリケーションはReact Routerに保持されている位置データに依存します:

history → React Router ↘ ↕ view Redux ↗

React Routerの代わりにreact-router-reduxを使用してストアから位置データを読み取ることはお勧めしません(潜在的に矛盾する真実のソースのため)。

私たちはより良いことができますか?

store.getState()store.dispatch()を使用して、Reduxでうまく動作するようにゼロから構築された代替ルーティングモデルを構築できますか?

私たちは絶対にできます、そしてそれはRedux-first routingと呼ばれています。

Redux-最初のルーティング

Redux-first routingは、ReduxをルーティングモデルのスターにするpushState routingのバリエーションです。

Redux-firstルーティングソリューションは、次の基準を満たします:

  • 場所はReduxストアで開催されています。
  • Reduxアクションをディスパッチすることにより、場所が変更されます。
  • アプリケーションは、ストアからのみ位置データを読み取ります。
  • 店舗とブラウザの履歴は舞台裏で同期しています。

ここでは、それがどのように見えるかの基本的な考え方です:

history ↕ Redux → router → view

待って、位置データのソースはまだ二つありませんか?

はい、しかし、ブラウザの履歴とReduxストアが同期していることを信頼できれば、ストアから位置データを読み取るだけのアプリケーションを構築できます。 ストア—その後、ビューのアプリケーションの観点から、真実の唯一のソースがあります。

Redux-firstルーティングをどのように達成するのですか?

まず、クライアント側のルーティングとReduxデータライフサイクルモデルの基本的な要素をマージすることによって、概念モデルを作成することから始

クライアント側ルーティングモデルの再訪

クライアント側のルーティングは、ナビゲーションから始まり、レンダリングで終わるマルチステッププロセスです。ルーティング自体は、そのプロセスの1つのステップにすぎません。 詳細を確認してみましょう:

  • ナビゲーション-すべては場所の変更から始まります。 ナビゲーションには、内部と外部の2種類があります。 内部ナビゲーションは、アプリ内から達成されます(例えば。 外部ナビゲーションは、ユーザーがブラウザのナビゲーションバーと対話するか、外部サイトからアプリケーションに入るときに発生します。
  • ナビゲーションへの応答—場所が変更されると、アプリケーションは新しい場所をルータに渡すことによって応答します。 古いルーティング技術はこれを達成するためにポーリングwindow.locationに依存していましたが、今日では便利なhistory.listenユーティリティがあります。
  • Routing—次に、新しい場所が対応するページコンテンツに一致します。 このステップを処理するコードはルーターと呼ばれ、一般的にはルート設定と呼ばれる一致するルートとページの入力パラメータを取ります。
  • レンダリング—最後に、コンテンツがクライアント上でレンダリングされます。 もちろん、このステップはReactのようなフロントエンドフレームワーク/ライブラリによって処理されるかもしれません。

ルーティングライブラリはルーティングモデルのすべての部分を処理する必要はないことに注意してください。

React RouterやVue Routerのようないくつかのライブラリは、Universal Routerのように、単一の側面(ルーティングのような)のみに関係しているため、他の側面に柔軟性を提供します:

ルーティングライブラリには、異なるスコープの責任がある場合があります。 (クリックすると拡大します)

Reduxデータライフサイクルモデルの再検討

Reduxは、おそらく導入を必要としない一方向のデータフロー/ライフサイクルモデルを誇っていますが、ここでは良い測定のための簡単な概要:

  • Action—状態の変更は、Reduxアクション(typeとオプションのペイロードを含むプレーンなオブジェクト)をディスパッチすることから始まります。
  • ミドルウェア—アクションはストアの一連のミドルウェアを通過し、アクションが傍受され、追加の動作が実行される可能性があります。 ミドルウェアは、Reduxアプリケーションの副作用を処理するために一般的に使用されます。
  • Reducer—アクションはルートreducerに到達し、ストアの次の状態を前の状態と受信したアクションの純粋な関数として計算します。 ルートレデューサーは、各ストアの状態のスライスを処理する個々のレデューサーで構成することができます。
  • New state—ストアはreducerによって返された新しい状態を保存し、変更をサブスクライバーに通知します(Reactではconnectを介して)。
  • レンダリング—最後に、ストア接続されたビューは、新しい状態に従って再レンダリングすることができます。

Redux-Firstルーティングモデルの構築

クライアント側のルーティングとReduxデータライフサイクルモデルの単方向性は、Redux-firstルーティングのためにレイアウトした基準を満たすマージされたモデ

このモデルでは、ルータはストアにサブスクライブされ、ナビゲーションはReduxアクションを介して実行され、ブラウザ履歴の更新はカスタムミドルウェアに このモデルの詳細を調べてみましょう:

  • Reduxアクションを使用した内部ナビゲーション—履歴APIを直接使用する代わりに、履歴ナビゲーションメソッドを反映する5つのナビゲーションアクションのいずれかをディスパッチすることによって内部ナビゲーションが実現されます。
  • ミドルウェアを介したブラウザ履歴の更新—ミドルウェアは、ナビゲーションアクションを傍受し、ブラウザ履歴の更新の副作用を処理するために使 新しい場所は、必ずしもまたは簡単に最初のブラウザの履歴を参照せずに知られていないので(例えば。 goアクションの場合)、ナビゲーションアクションが減速機に到達することが防止される。
  • ナビゲーションへの応答—実行フローは、新しい場所を含む第二のアクションをディスパッチすることにより、(ミドルウェアと外部ナビゲーションの両方か
  • Location Reducer—リスナーによってディスパッチされたアクションは、location reducerに到達し、ストアに場所を追加します。 位置レデューサーは、位置状態の形状も決定します。
  • Connected routing—店舗に接続されたルーターは、店舗内の場所の変更が通知されたときに、新しいページコンテンツを反応的に決定できます。
  • レンダリング—最後に、新しいコンテンツでページを再レンダリングすることができます。

これはRedux-firstルーティングを達成する唯一の方法ではないことに注意してください—いくつかのバリエーションは、ミドルウェアでストアエンハンサーや追加

先ほど見たモデルに続いて、コアAPIであるアクション、ミドルウェア、リスナー、リデューサーを実装しましょう。

historyパッケージを内部依存関係として使用し、ソリューションを段階的にビルドします。 あなたはむしろ最終的な結果と一緒に従うしたい場合は、ここでそれを表示することができます。

アクション

まず、履歴ナビゲーションメソッドを反映する5つのナビゲーションアクションを定義します:

// constants.jsexport const PUSH = 'ROUTER/PUSH';export const REPLACE = 'ROUTER/REPLACE';export const GO = 'ROUTER/GO';export const GO_BACK = 'ROUTER/GO_BACK';export const GO_FORWARD = 'ROUTER/GO_FORWARD';
// actions.jsexport const push = (href) => ({ type: PUSH, payload: href,});
export const replace = (href) => ({ type: REPLACE, payload: href,});
export const go = (index) => ({ type: GO, payload: index,});
export const goBack = () => ({ type: GO_BACK,});
export const goForward = () => ({ type: GO_FORWARD,});

ミドルウェア

次に、ミドルウェアを定義しましょう。 ナビゲーションアクションを傍受し、対応するhistoryナビゲーションメソッドを呼び出してから、アクションが減速機に到達するのを停止しますが、他のすべ:

// middleware.jsexport const routerMiddleware = (history) => () => (next) => (action) => { switch (action.type) { case PUSH: history.push(action.payload); break; case REPLACE: history.replace(action.payload); break; case GO: history.go(action.payload); break; case GO_BACK: history.goBack(); break; case GO_FORWARD: history.goForward(); break; default: return next(action); }};

以前にReduxミドルウェアの内部を書いたり調べたりする機会がなかった場合は、この紹介をチェックしてください。

履歴リスナー

次に、新しい位置情報を含む新しいアクションをディスパッチすることによってナビゲーションに応答するhistoryリスナーが必要です。

まず、新しいアクションタイプと作成者を追加しましょう。 場所の興味深い部分はpathnamesearch、および

// constants.jsexport const LOCATION_CHANGE = 'ROUTER/LOCATION_CHANGE';
// actions.jsexport const locationChange = ({ pathname, search, hash }) => ({ type: LOCATION_CHANGE, payload: { pathname, search, hash, },});

次に、リスナー関数を書いてみましょう:

// listener.jsexport function startListener(history, store) { history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

アプリケーションへの最初のエントリを考慮するために、最初のlocationChangeディスパッチを追加します(これは履歴リスナーによってピックアップされません):

// listener.jsexport function startListener(history, store) { store.dispatch(locationChange({ pathname: history.location.pathname, search: history.location.search, hash: history.location.hash, }));
 history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

Reducer

次に、位置reducerを定義しましょう。 単純な状態形状を使用し、減速機で最小限の作業を行います:

// reducer.jsconst initialState = { pathname: '/', search: '', hash: '',};
export const routerReducer = (state = initialState, action) => { switch (action.type) { case LOCATION_CHANGE: return { ...state, ...action.payload, }; default: return state; }};

アプリケーションコード

最後に、APIをアプリケーションコードに接続してみましょう:

// index.jsimport { combineReducers, applyMiddleware, createStore } from 'redux'import { createBrowserHistory } from 'history'import { routerReducer } from './reducer'import { routerMiddleware } from './middleware'import { startListener } from './listener'import { push } from './actions'
// Create the history objectconst history = createBrowserHistory()
// Build the root reducerconst rootReducer = combineReducers({ // ...otherReducers, router: routerReducer,}) // Build the middlewareconst middleware = routerMiddleware(history)
// Create the storeconst store = createStore(rootReducer, {}, applyMiddleware(middleware))
// Start the history listenerstartListener(history, store)
// Now you can read location data from the store!let currentLocation = store.getState().router.pathname
// You can also subscribe to changes in the location!let unsubscribe = store.subscribe(() => { let previousLocation = currentLocation currentLocation = store.getState().router.pathname
 if (previousLocation !== currentLocation) { // You can render your application reactively here! }})
// And you can dispatch navigation actions from anywhere!store.dispatch(push('/about'))

そして、それはそれにあるすべてです! 私たちの小さな(100行以下のコード)APIを使用して、Redux-firstルーティングのすべての基準を満たしています:

  • 場所はReduxストアで開催されています。 ✔
  • Reduxアクションをディスパッチすることにより、場所が変更されます。 §
  • アプリケーションは、ストアからのみ位置データを読み取ります。 ▲
  • ストアとブラウザの履歴は舞台裏で同期しています。 ✔

ここですべてのファイルを一緒に表示する—あなたのプロジェクトにそれらをインポートして自由に感じるか、独自の実装を開発するための出発点とRedux-first-routingパッケージ

また、APIをredux-first-routingパッケージにまとめました。npm installと同じ方法で使用できます。

mksarge/redux-first-routing
redux-first-routing—Redux-first routingを達成するための最小限のフレームワークに依存しないベース。githubだcom

ここで構築したものと同様の実装が含まれていますが、query-stringパッケージを介したクエリ解析の注目すべき追加が含まれています。

Wait-実際のルーティングコンポーネントはどうですか?

redux-first-routingはルーティングモデルのナビゲーション面にのみ関係していることに気づいたかもしれません:

ルーティングモデルの他の側面からナビゲーションの側面を分離することにより、redux-first-routingはルーターに依存しないし、フレームワークに依存しない柔軟性を得ました。

したがって、Universal Routerのようなライブラリとペアリングして、任意のフロントエンドフレームワーク用の完全なRedux-firstルーティングソリューションを作成できます:

redux-first-routing+universal-routerの使用を開始するには、ここをクリックしてください。

または、選択したフレームワークのための独断的なバインディングを構築することができます。

Reactで宣言的なナビゲーションとルーティングのためのstore-connectedコンポーネントをどのように構築するかを見て、探査を終了しましょう。

宣言型ナビゲーション

ナビゲーションには、React Routerや他のReact routingソリューションのものと同様のstore-connected<Link/>コンポーネントを使用できます。

アンカー要素<a/>とdispatchches aプッシュアクションのデフォルトの動作をオーバーライドします:

// Link.jsimport React from 'react';import { connect } from 'react-redux';import { push as pushAction, replace as replaceAction } from './actions';
const Link = (props) => { const { to, replace, children, dispatch, ...other } = props;

 const handleClick = (event) => { // Ignore any click other than a left click if ((event.button && event.button !== 0) || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || event.defaultPrevented === true) { return; } // Prevent the default behaviour (page reload, etc.) event.preventDefault();
 // Dispatch the appropriate navigation action if (replace) { dispatch(replaceAction(to)); } else { dispatch(pushAction(to)); } };
 return ( <a href={to} onClick={handleClick} {...other}> {children} </a>);};
export default connect()(Link);

ここでより完全な実装を見つけることができます。

宣言的ルーティング

ナビゲーションコンポーネントにはあまりありませんが、ルーティングコンポーネントを設計する方法は無数にあります。

ルータとは何ですか?

一般的には、ルータを関数として表示するか、二つの入力と一つの出力を持つブラックボックスとして表示することができます:

route configuration ↘ matched content current location ↗

ルーティングとその後のレンダリングは別々のステップで行われるかもしれませんが、Reactはそれらを宣言型ルーティングAPIにバンドルすることを簡単 これを達成するための2つの戦略を見てみましょう。

戦略1:モノリシック<Router/>コンポーネント

モノリシック、ストア接続<Router/>コンポーネントを使用することができます。:

  • propsを介してルート設定オブジェクトを受け入れる
  • Reduxストアから位置データを読み取ります
  • 場所が変更されるたびに新しいコンテンツを計算します
  • コンテンツを必要に応じてレンダリング/再レンダリングします。

ルート設定は、一致するすべてのパスとページを含むプレーンなJavaScriptオブジェクトである可能性があります(集中ルート設定)。

これがどのように見えるかは次のとおりです:

const routes = 
React.render( <Provider store={store}> <Router routes={routes}> </Provider>, document.getElementById('app'))

かなり簡単、右か。 ネストされたJSXルートは必要ありません—単一のルート設定オブジェクトと単一のルーターコンポーネントだけです。

この戦略が魅力的であれば、redux-json-routerライブラリで私のより完全な実装をチェックしてください。 これはredux-first-routingをラップし、これまでに調べた戦略を使用して宣言的なナビゲーションとルーティングのためのReactバインディングを提供します。

mksarge/redux-json-router
redux-json-router-Declarative,Redux-React/Reduxブラウザの最初のルーティングapplications.github.com

戦略2: Composable<Route/>components

モノリシックコンポーネントはReactで宣言的ルーティングを達成する簡単な方法かもしれませんが、それは間違いなく唯一の方法ではありません。

Reactの合成可能な性質は、JSXを使用して分散化された方法でルートを定義するというもう一つの興味深い可能性を可能にします。 もちろん、主な例はReact Routerの<Route/>APIです:

React.render( <BrowserRouter> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </BrowserRouter>

他のルーティングライブラリもこの考えを探求しています。 私はそれを行う機会はありませんでしたが、redux-first-routingパッケージの上に同様のAPIを実装できなかった理由はありません。

<BrowserRouter/>, the &ltによって提供される位置データに依存するのではなく、ルート/>コンポーネントcould simplyストアに接続します:

React.render( <Provider store={store}> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </Provider>

それがあなたが構築したり使用したりすることに興味があるものなら、私にコメントで知らせてください! さまざまなルート設定戦略の詳細については、React Routerのwebサイトでこの紹介をチェックしてください。

結論

この調査がクライアント側のルーティングに関する知識を深めるのに役立ち、Reduxの方法でそれを達成するのがいかに簡単かを示してくれたことを願っています。

完全なReduxルーティングソリューションをお探しの場合は、readmeに記載されている互換性のあるルータでredux-first-routingパッケージを使用できます。 また、カスタマイズされたソリューションを開発する必要がある場合は、この投稿がそうするための良い出発点を与えてくれることを願っています。

ReactとReduxのクライアント側ルーティングについてもっと知りたい場合は、以下の記事をチェックしてください。:

  • Urlに話をさせてくださいTyler Thompson
  • Konstantin Tarkus
  • React Routerは必要ないかもしれません。 James K.Nelson
  • react-router-reduxの問題で無数の有益な議論によって。

クライアント側のルーティングは無限のデザインの可能性を持つスペースであり、私がここで共有したものと同様のアイデアで遊んだことがあると確信しています。 あなたが会話を続けたいのであれば、私はコメントやTwitter経由であなたと接続するために喜んでいるでしょう。 読んでくれてありがとう!

編集22/06/17: また、インテリジェントなアクションタイプを使用して強力なルーティング機能を実現する別のプロジェクトであるredux-first-routerのこの記事も

コメントを残す

メールアドレスが公開されることはありません。