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をクリーンアンインストールする方法の詳細な説明

推薦する

CentOS 7 での Docker プロキシの設定 (Linux での Systemd サービスの環境変数設定)

Docker デーモンは、 HTTP_PROXY 、 HTTPS_PROXY 、およびNO_PRO...

CSS の子要素を親要素と高い一貫性を持たせる方法

絶対位置決め方式: (1)親要素を相対配置に設定します。親要素の高さを指定しない場合は、左の子要素の...

Ubuntu 20.04 IPアドレスを変更する方法の例

例:本日、前回のオフィスコラボレーションプラットフォーム実験の続きをしていたところ、仮想マシンは以前...

11 の素晴らしい JavaScript コード リファクタリングのベスト プラクティスの概要

目次1. 関数の抽出2. 重複した条件付きスニペットを結合する3. 条件分岐文を関数に抽出する4. ...

docker に php-fpm サービス/拡張機能/構成をインストールする詳細なチュートリアル

macにbrewを使ってphp56をインストールしたところ、 opensslがバージョン1.1だった...

HTML にネストされた div の無効なマージンに対する解決策

div がネストされているときに margin が機能しない問題の解決策を次に示します。さて、マージ...

MySQL 5.7.18 バージョンの無料インストール構成チュートリアル

MySQLはインストール版と無料インストール版に分かれていますインストール版の拡張子はmsi、無料イ...

JavaScriptはイベントリスナーをイベント委任にバッチで追加します。詳細なプロセス

1. イベント委任とは何ですか?イベント委譲: イベントバブリングの特性を利用して、子要素に登録すべ...

LinkedIn がウェブサイト閲覧を簡素化するためにリニューアル

ビジネス ソーシャル ネットワーキング サイト LinkedIn は最近、ナビゲーション バーとユー...

HTMLページ間で値を渡す問題の解決策

初めてこのエッセイを使ったとき、私はかなりぎこちなく感じましたhtmlファイルコードをコピーコードは...

Windows および Linux での Redis のインストールとデーモン設定

# Windows および Linux 上の Redis のインストール デーモン構成Redis の...

高性能なウェブサイトのための14のテクニック

オリジナル: http://developer.yahoo.com/performance/rule...

divは、自動入力スタイルをブロックする入力ボックスとして入力を使用せずにコンテンツを入力できます。

今日、私は公開用の動的なウィンドウ スタイルを設計しましたが、マウスで入力をクリックしたときにブラウ...

JavaScriptは双方向リンクリストプロセス分析を実装します

目次1. 二重連結リストとは何か2. 双方向リンクリストのカプセル化3. 双方向リンクリストの一般的...