ReactRouterの実装

ReactRouterの実装

ReactRouterの実装

ReactRouterReactのコアコンポーネントです。主にReactのルーティングマネージャーとして使用され、 UIURL同期を維持します。シンプルなAPIと、コードバッファの読み込み、動的なルートのマッチング、正しい位置遷移処理の確立などの強力な機能を備えています。

説明する

React Routerhistoryオブジェクトに基づいて構築されています。簡単に言うと、 historyオブジェクトは、ブラウザのアドレスバーの変更を監視し、 URLを解析してlocationオブジェクトに変換する方法を知っています。 routerそれを使用してルートを一致させ、最終的に該当するコンポーネントを正しくレンダリングします。よく使用されるhistory形式は、 Browser HistoryHash HistoryMemory Historyの 3 つです。

ブラウザ履歴

Browser HistoryReact Routerを使用するアプリケーションに推奨されるhistoryです。ブラウザ内のHistoryオブジェクトのpushStatereplaceStateなどのAPIpopstateイベントを使用してURLを処理します。 https://www.example.com/pathような実際のURLを作成できます。ページがジャンプしたときにページを再読み込みする必要はなく、もちろんサーバーにリクエストは行われません。もちろん、 historyモードでは、ホームページ以外のリクエストや、リフレッシュ時にバックエンドから返されるリソースをサポートするために、バックエンド構成のサポートが必要です。アプリケーションはシングルページクライアントアプリケーションであるため、バックエンドが正しく構成されていない場合、ユーザーがブラウザでURLに直接アクセスすると404が返されるため、すべての状況をカバーするためにサーバー上に候補リソースを追加する必要があります。URL URLどの静的リソースにも一致しない場合は、 Nginxでの構成など、同じindex.htmlアプリケーション依存関係ページを返す必要があります。

位置 / {
 try_files $uri $uri/ /index.html;
}

ハッシュ履歴

Hash記号#は、もともとURL内の Web ページの場所を示すために使用されていました。たとえば、 https://www.example.com/index.html#printexampleindex.htmlprint場所を表します。ブラウザがこのURLを読み取ると、 print場所が表示領域に自動的にスクロールされます。アンカー ポイントは通常、 <a>タグのname属性または<div>タグのid属性を使用して指定されます。
アンカーの位置は、 window.location.hashプロパティを通じて読み取ることができ、 Hashの変更に対するhashchangeリスナー イベントを追加できます。 Hashが変更されるたびに、ブラウザーのアクセス履歴に記録が追加されます。また、 Hash URLに表示されますが、 HTTPリクエストには含まれません。つまり、 #とそれ以降の文字は、リソースやデータを要求するためにサーバーに送信されません。これは、ブラウザーの動作をガイドするために使用され、サーバーには影響しません。したがって、 Hash変更してもページは再読み込みされません。
ReactRouterの役割は、ページを再リクエストせずにURLを変更してページビューを更新することで、コンポーネントを動的にロードおよび破棄することです。簡単に言えば、アドレスバーのアドレスは変更されていますが、まったく新しいページではなく、前のページの一部が変更されています。これは、 SPAシングルページアプリケーションの特徴でもあります。そのすべてのアクティビティは1つのWebページに限定されています。遅延ロードされないページは、 Webページが初期化されるときに、対応するHTMLJavaScript 、およびCSSファイルのみをロードします。ページがロードされると、 SPAページを再ロードしたりジャンプしたりせず、 JavaScriptを使用してHTMLを動的に変換します。デフォルトのHashモードは、アンカーを介したルーティングを実装し、コンポーネントの表示と非表示を制御して、ページジャンプに似たインタラクションを実現します。

記憶の履歴

Memory Historyアドレスバーで操作または読み込まれないため、サーバーレンダリングがどのように実装されているかを説明しています。また、テストやReact Nativeなどの他のレンダリング環境にも非常に適しています。他の 2 つのHistoryとの違いは、作成する必要があることです。これにより、テストが容易になります。

定数履歴 = createMemoryHistory(場所);

成し遂げる

非常にシンプルなBrowser HistoryモードとHash Historyモードを実装してみましょう。H5 H5 pushStateメソッドはローカルファイルプロトコルfile://では実行できないため、実行するにはhttp://環境を構築する必要があります。 webpackNginxApacheなどを使用できます。 Browser Historyモードのルーティングに戻ると、 pushState()replaceState()などのH5が提供するメソッドやpopstateなどのイベントのおかげで、ページを更新せずにhistoryルーティングジャンプを実装できます。これらのメソッドもルーティングパスを変更できますが、ページジャンプは行いません。もちろん、バックエンドが適切に構成されていない場合は、ルーティングが適応された後にページが更新され、 404プロンプトが表示されます。 Hash Historyモードの場合、実装のアイデアは似ていますが、主にpushStateなどのH5APIを使用しないことと、リスニングイベントが異なることです。hashchange hashchangeの変更をリッスンすることで、対応するlocation.hashを取得し、対応するビューを更新します。

<!-- ブラウザ履歴 -->
<!DOCTYPE html>
<html lang="ja">

<ヘッド>
 <メタ文字セット="UTF-8">
 <title>ルーター</title>
</head>

<本文>
 <ul>
  <li><a href="/home" rel="external nofollow" >ホーム</a></li>
  <li><a href="/about" rel="external nofollow" >について</a></li>
  <div id="ルートビュー"></div>
 </ul>
</本文>
<スクリプト>
 関数ルーター() {
  this.routeView = null; // コンポーネントホストビューコンテナ this.routes = Object.create(null); // 定義されたルート }

 // ルートマッチングイベントをバインドする Router.prototype.route = function (path, callback) {
  this.routes[path] = () => this.routeView.innerHTML = callback() || "";
 };

 // InitializeRouter.prototype.init = function(root, rootView) {
  this.routeView = rootView; //ビューコンテナを指定する this.refresh(); //ビューを初期化して更新する root.addEventListener("click", (e) => { //ルートへのイベント委譲
   if (e.target.nodeName === "A") {
    e.preventDefault();
    history.pushState(null, "", e.target.getAttribute("href"));
    this.refresh(); // ビューを更新するためのトリガー}
  })
  // ユーザーの「戻る」と「進む」のクリックをリッスンします // pushState と replaceState は popstate イベントをトリガーしません window.addEventListener("popstate", this.refresh.bind(this), false); 
 };

 // ビューを更新するRouter.prototype.refresh = function () {
  path = location.pathname とします。
  console.log("更新", パス);
  this.routes[path] の if(this.routes[path]());
  それ以外の場合は this.routeView.innerHTML = "";
 };

 window.Router = 新しい Router();
 
 Router.route("/home", 関数() {
  「ホーム」を返します。
 });
 Router.route("/about", 関数() {
  「about」を返します。
 });

 Router.init(ドキュメント、document.getElementById("routeView"));

</スクリプト>
</html>
<!-- ハッシュ履歴 -->
<!DOCTYPE html>
<html lang="ja">

<ヘッド>
 <メタ文字セット="UTF-8">
 <title>ルーター</title>
</head>

<本文>
 <ul>
  <li><a href="#/home" rel="external nofollow" >ホーム</a></li>
  <li><a href="#/about" rel="external nofollow" >について</a></li>
  <div id="ルートビュー"></div>
 </ul>
</本文>
<スクリプト>
 関数ルーター() {
  this.routeView = null; // コンポーネントホストビューコンテナ this.routes = Object.create(null); // 定義されたルート }

 // ルートマッチングイベントをバインドする Router.prototype.route = function (path, callback) {
  this.routes[path] = () => this.routeView.innerHTML = callback() || "";
 };

 // InitializeRouter.prototype.init = function(root, rootView) {
  this.routeView = rootView; // ビュー コンテナーを指定します this.refresh(); // 初期化トリガー // 更新するために hashchange イベントをリッスンします window.addEventListener("hashchange", this.refresh.bind(this), false); 
 };

 // ビューを更新するRouter.prototype.refresh = function () {
  ハッシュ = location.hash;
  console.log("更新", ハッシュ);
  this.routes[ハッシュ]を返し、this.routes[ハッシュ]を返します。
  それ以外の場合は this.routeView.innerHTML = "";
 };

 window.Router = 新しい Router();
 
 Router.route("#/home", 関数() {
  「ホーム」を返します。
 });
 Router.route("#/about", 関数() {
  「about」を返します。
 });

 Router.init(ドキュメント、document.getElementById("routeView"));

</スクリプト>
</html>

分析する

  • ReactRouterの実装を見てみましょう。 commit ideef79d5TAG 4.4.0です。その前に、 historyライブラリを理解する必要があります。 history historyは、 ReactRouter依存するwindow.historyの拡張バージョンです。使用される主なオブジェクトは、現在のURLpathの一致の結果を表すmatchオブジェクトです。location locationは、 window.locationに基づくhistoryライブラリの派生物です。
  • ReactRouterルーティングを複数のパッケージに分割します。react react-routerは一般的なルーティング ロジックを担当し、 react-router-domはブラウザー ルーティング管理を担当し、 react-router-nativereact-nativeルーティング管理を担当します。
  • BrowserRouterコンポーネントを例に挙げてみましょう。 react-router-domでは、 BrowserRouter内部的にグローバルhistoryオブジェクトを作成する高階コンポーネントです。これにより、ルート全体の変更を監視し、 history react-routerRouterコンポーネントにpropsとして渡すことができます。次に、 Routerコンポーネントは、このhistoryの属性をcontextとして子コンポーネントに渡します。
// packages\react-router-dom\modules\HashRouter.js 10行目
BrowserRouterクラスはReact.Componentを拡張します。
 履歴 = createHistory(this.props);

 与える() {
 <Router history={this.history} children={this.props.children} /> を返します。
 }
}

次に、 Routerコンポーネントに進みます。 Routerコンポーネントは、 React Context環境を作成します。これは、 contextの助けを借りてcontextRouteに渡します。これは、 RouterすべてのRoute外部にある必要がある理由も説明しています。 RoutercomponentWillMounthistory.listenが追加され、ルーティングの変更を監視し、コールバック イベントを実行して、ここでsetStateをトリガーできるようになります。 setStateが呼び出されると、つまりルートが変更されるたびに->最上位Routerのコールバック イベントがトリガーされます-> Router setStateを実行し-> nextContextを渡します。この時点で、 contextには最新のlocationが含まれています->次のRouteは新しいnextContextを取得し、レンダリングするかどうかを決定します。

// 行 packages\react-router\modules\Router.js 行 10
クラス Router は React.Component を拡張します {
 静的computeRootMatch(パス名) {
 戻り値: { パス: "/", url: "/", パラメータ: {}, isExact: パス名 === "/" };
 }

 コンストラクタ(props) {
 スーパー(小道具);

 この状態 = {
  場所: props.history.location
 };

 // これはちょっとしたハックです。位置情報を聞き始める必要があります
 // <Redirect> がある場合に備えて、コンストラクタ内で変更します
 // 初期レンダリング時に。存在する場合は、置き換え/プッシュされます
 // それらはマウントされ、cDMは親よりも先に子で発火するので、
 // <Router> がマウントされる前に新しい場所を取得します。
 this._isMounted = false;
 this._pendingLocation = null;

 静的コンテキストの場合
  this.unlisten = props.history.listen(location => {
  if (this._isMounted) {
   this.setState({ location });
  } それ以外 {
   this._pendingLocation = 場所;
  }
  });
 }
 }

 コンポーネントマウント() {
 this._isMounted = true;

 if (this._pendingLocation) {
  this.setState({ location: this._pendingLocation });
 }
 }

 コンポーネントのマウントを解除します(){
 this.unlisten() は、次の例のように機能します。
 }

 与える() {
 戻る (
  <ルーターコンテキスト.プロバイダー
  children = {this.props.children || null}
  値={{
   履歴: this.props.history、
   場所: this.state.location、
   一致: Router.computeRootMatch(this.state.location.pathname)、
   静的コンテキスト: this.props.staticContext
  }}
  />
 );
 }
}

これを使用するときは、 Routerを使用してRouteをネストするので、 Routeコンポーネントにたどり着きます。 Routeの役割は、ルートを一致させて、コンポーネントのpropsに渡してレンダリングすることです。 Route 、上位レベルのRouterから渡されたcontextを受け入れます。 Routerhistoryページ全体のルートの変更を監視します。ページがジャンプすると、 historyによって listen イベントがトリガーされ、 Router nextContext下位に渡します。これにより、 Routepropscontextが更新され、現在のRoutepath locationと一致するかどうかが判断されます。一致する場合はレンダリングされ、そうでない場合はレンダリングされません。一致するかどうかの基準となるのはcomputeMatch関数で、これについては後で分析します。ここでは、一致が失敗した場合、 matchnullになることを知っておく必要があります。一致が成功した場合、 matchの結果がpropsの一部として使用され、 renderでレンダリングされるコンポーネントに渡されます。 Route<Route component><Route render><Route children>の 3 種類のrender propsを受け入れます。このとき注意すべき点は、渡されたcomponentがインライン関数の場合、 props.componentは毎回新しく作成されるため、 React diff中に新しいコンポーネントが入ったと認識し、古いコンポーネントunmount re-mountことになります。このとき、 renderを使用する必要があります。ラップされたcomponent要素のレイヤーがない場合、 renderが展開された後の要素タイプは毎回同じであるため、 re-mount発生せず、 children re-mountません。

// \packages\react-router\modules\Route.js 17行目
クラス Route は React.Component を拡張します {
 与える() {
 戻る (
  <ルーターコンテキスト.コンシューマー>
  {コンテキスト => {
   invariant(context, "<Router> の外部では <Route> を使用しないでください");

   const location = this.props.location || context.location;
   定数マッチ = this.props.computedMatch
   ? this.props.computedMatch // <Switch> はすでに一致を計算しています
   : this.props.path
    ? matchPath(location.pathname, this.props)
    : コンテキストに一致します。

   const props = { ...コンテキスト、場所、一致 };

   let { children, component, render } = this.props;

   // Preactは空の配列を子として使用します
   // デフォルトなので、その場合は null を使用します。
   Array.isArray(children) && children.length === 0 の場合 {
   子 = null;
   }

   if (typeof children === "function") {
   子供 = 子供(プロパティ);
   // ...
   }

   戻る (
   <RouterContext.Provider 値 = {props}>
    {子供 && !isEmptyChildren(子供)
    ? 子供たち
    : props.match
     ? 成分
     React.createElement(コンポーネント、プロパティ)
     : 与える
      ? レンダリング(props)
      : ヌル
     : ヌル}
   </ルーターコンテキスト.プロバイダー>
   );
  }}
  </ルーターコンテキスト.コンシューマー>
 );
 }
}

実際、おそらく最も多く書くタグはLinkタグなので、 <Link>コンポーネントをもう一度見てみましょう。Link Link最終的にジャンプ先の要素を囲むaタグを作成することがわかります。このaタグのhandleClickイベントでは、 preventDefaultによってデフォルトのジャンプが禁止されるため、実際にはここでのhrefには実質的な効果はありませんが、ジャンプ先のページのURLを示し、より適切なhtmlセマンティクスを実現できます。 handleClickでは、 preventDefaultではないクリック、左クリック、 _blankジャンプではないクリック、他のファンクションキーが押されていないクリックに対してpreventDefaultを実行し、 historypush 。これは、ルートの変更とページジャンプが互いに関連していないことも前述したとおりです。ReactRouter ReactRouter Linkhistoryライブラリのpushを通じてHTML5 historypushStateを呼び出しますが、これはルートを変更するだけで、他には何もしません。 Routerlistenでは、ルートの変更をリッスンし、 contextを通じてpropsnextContextを更新して、基礎となるRouteが再一致し、レンダリングする必要がある部分の更新を完了できるようにします。

// packages\react-router-dom\modules\Link.js 14行目
クラスLinkはReact.Componentを拡張します。
 handleClick(イベント、履歴) {
 this.props.onClick の場合、 this.props.onClick(イベント);

 もし (
  !event.defaultPrevented && // onClick はデフォルトで禁止
  event.button === 0 && // 左クリック以外はすべて無視
  (!this.props.target || this.props.target === "_self") && // ブラウザに "target=_blank" などを処理させます。
  !isModifiedEvent(event) // 修飾キーによるクリックを無視する
 ){
  イベントをデフォルトにしない();

  const メソッド = this.props.replace ? history.replace : history.push;

  メソッド(this.props.to);
 }
 }

 与える() {
 const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars

 戻る (
  <ルーターコンテキスト.コンシューマー>
  {コンテキスト => {
   invariant(context, "<Link> を <Router> の外部で使用しないでください");

   定数場所 =
   typeof を === "string" にする
    ? createLocation(to, null, null, context.location)
    : に;
   const href = location ? context.history.createHref(location): "";

   戻る (
   <a
    {...休む}
    onClick={イベント => this.handleClick(イベント、コンテキスト.履歴)}
    href={href}
    ref={内部参照}
   />
   );
  }}
  </ルーターコンテキスト.コンシューマー>
 );
 }
}

毎日の質問

https://github.com/WindrunnerMax/EveryDay

参照する

https://zhuanlan.zhihu.com/p/44548552 https://github.com/fi3ework/blog/issues/21 https://juejin.cn/post/6844903661672333326 https://juejin.cn/post/6844904094772002823 https://juejin.cn/post/6844903878568181768 https://segmentfault.com/a/1190000014294604 https://github.com/youngwind/blog/issues/109 http://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html

ReactRouterの実装方法については以上です。ReactRouterの実装方法については、123WORDPRESS.COMの過去記事を検索するか、引き続き以下の関連記事をご覧ください。今後とも123WORDPRESS.COMをよろしくお願いいたします。

以下もご興味があるかもしれません:
  • React.Childrenの詳しい使い方
  • React Hooksの使用例
  • React Native の基本原則の深い理解 (Bridge of React Native)
  • React+Koa によるファイルアップロードの実装例
  • Reactフックとzarmコンポーネントライブラリ構成に基づいてh5フォームページを開発するためのサンプルコード
  • React antd タブの切り替えによりサブコンポーネントが繰り返し更新される
  • ReactJs 基礎チュートリアル - 基本編
  • React.cloneElement の使い方の詳しい説明

<<:  Linux での MySQL 5.7.16 無料インストール バージョンのグラフィック チュートリアル

>>:  Dockerをクリーンアンインストールする方法の詳細な説明

推薦する

CSS エラスティック ボックス flex-grow、flex-shrink、flex-basis の詳細な説明

3 つの属性 flex-grow、flex-shrink、flex-basis の機能は次のとおりで...

CSS の position 属性の値に関する研究 (概要)

CSS の位​​置属性は要素の配置タイプを指定し、上、下、左、右を使用して要素を具体的に配置します...

MySQL max_allowed_pa​​cket 設定

max_allowed_pa​​cket は、受け入れるパケットのサイズを設定するために使用される ...

入力のid属性とname属性の違いの例

長い間ウェブサイトを作ってきましたが、入力時のnameとidの違いがまだわかりません。最近jQuer...

MySQL インデックス プッシュダウンを 5 分で学ぶ

目次序文インデックス プッシュダウンとは何ですか?インデックスプッシュダウン最適化の原理インデックス...

MySQLの遅いクエリ問題の詳細な分析データ送信

例を通して、MySQL のデータ テーブル送信のクエリが遅い問題の解決策を共有しました。最近、コード...

MySQLインデックスの基本構文

インデックスはソートされたデータ構造です。 where 条件での検索や order by 条件での並...

JS がビデオ弾幕効果を実現

これを実現するには、ES6 モジュール開発とオブザーバー モードを使用します。オブザーバー パターン...

node.jsのコアモジュールとは

目次グローバルオブジェクトグローバルオブジェクトとグローバル変数プロセスコンソール一般的なツールユー...

HTML における src と href の違いについての簡単な説明

簡単に言うと、srcは「このリソースをロードしたい」という意味で、hrefは「このリソースに関連付け...

フォント名に従ってフォントを呼び出すと、ブラウザに必要なフォントが表示されます。

質問 1: ブラウザに必要なフォントを表示するように指示するにはどうすればよいでしょうか? フォント...

HTMLテーブルタグの詳しい解説(初心者向け)

表> <TR> <TD> <TH> <キャプション&...

vue の webpack -v エラー解決の概要

XiaobaiはVueについて学び、次にwebpackについて学び、そしてさまざまなものをインストー...

PHPのmail()関数を使用してメールを送信する

PHPのメール関数を使用してメールを送信するmail()関数はメールサーバーに接続し、サーバーと対話...

Ubuntu で apt-get を使用して mysql をインストールおよび完全にアンインストールする方法の詳細な説明

1. mysqlをインストールします。 udo apt-getでmysql-serverをインストー...