React Router 入门从现在开始

导引页

    • 今から始めるReact入門 〜 React の基本

今から始めるReact入門 〜 React Router 編 ←★ここ
今から始めるReact入門 〜 flux編
今から始めるReact入門 〜 Redux 編: immutability とは
今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する
今から始めるReact入門 〜 Redux 編: Redux アプリケーションを作成する
今から始めるReact入門 〜 Mobx 編

关于单页面应用(SPA)

这次我们想要学习使用react-router来创建基于声明性语法的单页应用(SPA)。
React Router是一个将UI和URL同步的库。例如,当访问者访问React应用程序时,如果访问的是http://app.example.com/,则会呈现Home组件;如果访问的是http://app.example.com/address,则会呈现显示地址的组件。通过使用React Router,我们可以轻松地执行这样的操作。

在本文中使用的文件

本文使用的文件可以在以下的存储库中找到。

    https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/00_start_point/react_router

创建项目

我们将创建一个全新的项目,与上一次的项目完全不同。
这次的应用程序将使用Bootstrap主题,但是Bootstrap通常需要jQuery来创建动画效果,但由于这次只是一个简单的示例,所以不需要使用jQuery进行动画效果,因此不加载jQuery。

$ mkdir react_router
$ cd react_router
$ npm init -y
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader \
        webpack webpack-cli webpack-dev-server \
        react react-dom \
        react-router react-router-dom

在package.json的scripts中,使用start命令来设置启动webpack-dev-server。

  "scripts": {
    "start": "webpack-dev-server --content-base src --mode development --inline",    // <- 追加
    ......
  },

这次,我们打算将预先完成的静态Web页面转移到React(JavaScript)端,以实现动态渲染。
请参考GitHub仓库准备以下的文件。我们将从以下状态开始进行应用程序开发。

var debug   = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path    = require('path');

module.exports = {
  context: path.join(__dirname, "src"),
  entry: "./js/client.js",
  module: {
    rules: [{
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      use: [{
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-react', '@babel/preset-env']
        }
      }]
    }]
  },
  output: {
    path: __dirname + "/src/",
    filename: "client.min.js",
    publicPath: '/'
  },
  devServer: {
    historyApiFallback: true
  },
  plugins: debug ? [] : [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
  ],
};
import React from "react";

export default class Layout extends React.Component {
  render() {
    return (
      <h1>KillerNews.net</h1>
    );
  }
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>React</title>
  <!-- Bootstrap Core CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cerulean/bootstrap.min.css" rel="stylesheet">

  <!-- Custom Fonts -->
  <!-- <link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
  <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
</head>

<body>

  <!-- Navigation -->
  <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <!-- Collect the nav links, forms, and other content for toggling -->
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav">
          <li>
            <a href="#">Featured</a>
          </li>
          <li>
            <a href="#">Archives</a>
          </li>
          <li>
            <a href="#">Settings</a>
          </li>
        </ul>
      </div>
      <!-- /.navbar-collapse -->
    </div>
  </nav>
  <!-- Page Content -->
  <div class="container" style="margin-top: 60px;">
    <div class="row">
      <div class="col-lg-12">
        <div id="app"></div>
      </div>
    </div>
    <!-- Call to Action Well -->
    <div class="row">
      <div class="col-lg-12">
        <div class="well text-center">
          Ad spot goes here
        </div>
      </div>
      <!-- /.col-lg-12 -->
    </div>
    <!-- /.row -->
    <!-- Content Row -->
    <div class="row">
      <div class="col-md-4">
        <h2>Heading 1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
      <div class="col-md-4">
        <h2>Heading 2</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
      <div class="col-md-4">
        <h2>Heading 3</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
    </div>
    <!-- /.row -->
    <!-- Footer -->
    <footer>
      <div class="row">
        <div class="col-lg-12">
          <p>Copyright &copy; KillerNews.net</p>
        </div>
      </div>
    </footer>
  </div>

  <!-- /.container -->
  <script src="client.min.js"></script>
</body>
</html>
import React from "react";
import ReactDOM from "react-dom";

import Layout from "./pages/Layout";

const app = document.getElementById('app');

ReactDOM.render(<Layout />, app);

如果准备好了,请先启动Web服务器并确认页面。

$ npm start
React_ReactRouter0001.png

我想用React router来将此页面设为SPA并动态地改变页面的呈现方式。

安装React Router

React 環境建立完成后,接下来需要安装与 react-router 相关的包。

$ npm install --save-dev react-router react-router-dom
$ # react-router v4 からはreact-router-dom も必要になります

安装必要的库后,我们将创建每个组件。
首先,我们将创建一个用于渲染文章的特色组件。

import React from "react";

export default class Featured extends React.Component {
  render() {
    return (
      <h1>Featured</h1>
    );
  }
}
import React from "react";

export default class Archives extends React.Component {
  render() {
    return (
      <h1>Archives</h1>
    );
  }
}
import React from "react";

export default class Settings extends React.Component {
  render() {
    return (
      <h1>Settings</h1>
    );
  }
}

各组件的基础已经创建完成。接下来我们将编辑 client.js 文件,使用 React Router。

 import React from "react";
 import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
+import Featured from "./pages/Featured";
+import Archives from "./pages/Archives";
+import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
-ReactDOM.render(<Layout />, app);
+ReactDOM.render(
+  <Router>
+    <Layout>
+      <Route exact path="/" component={Featured}></Route>
+      <Route path="/archives" component={Archives}></Route>
+      <Route path="/settings" component={Settings}></Route>
+    </Layout>
+  </Router>,
+app);

如果您列出组件,您可以更直观地了解React Router的特点。在React Router中,当用户访问/、/archives和/settings路径时,可以将对应的组件分别指定为Featured、Archives和Settings,就如上所述。
此外,请注意上述的React Router只有一个exact关键字附加在上面。
这意味着当用户访问的路径与/精确匹配时,才会显示该组件。
这将成为访问http://app.example.com/时显示的组件。
如果没有这个exact关键字,无论是/foo还是/bar还是/archives,Featured组件都会显示出来,所以请注意。
总之,当用户访问/archives路径时,Featured组件和Archives组件都会显示出来。
另外,使用exact关键字指定的组件与React Router v3中的IndexRoute相当,请在React Router v4中使用exact关键字。

当用户访问 “/archives/foo” 或 “/archives/bar” 时, 将显示相应的组件。

在了解了React Router的大致特点之后,让我们再次运行npm start命令,确认一下http://localhost:8080是否正常显示。如果能够以与最初相同的布局显示且没有错误,那么就可以了。至少到这里,我们已经完成了进入React Router的下一步的准备工作。

添加链接

让我们修改Layout.js,添加一个指向Archive和Settings的链接。

 import React from "react";
+import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
+      <div>
         <h1>KillerNews.net</h1>
+        {this.props.children}
+        <Link to="/archives">archives</Link>,
+        <Link to="/settings">settings</Link>
+      </div>
     );
   }
 }

通过上述描述,每次点击链接时,Route组件嵌入到client.js中并传递给Layout组件,这样Layout组件就可以从this.props.children中引用Route组件。


ReactDOM.render(
  <Router>
    <Layout>                                                 // (2) Layout に渡り、Layout からthis.props.children で参照できる
      <Route exact path="/" component={Featured}></Route>
      <Route path="/archives" component={Archives}></Route>  // (1) 例えばLayout のarchives をクリックするとArchives ...
      <Route path="/settings" component={Settings}></Route>
    </Layout>
  </Router>,
app);

让我们再次在这里显示http://localhost:8080,并分别点击”archives”和”settings”链接。
每个”Archives”组件和”Settings”组件将会交替显示出来。

React_ReactRouter0002.gif

试试用按钮装饰链接

 import React from "react";
 import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives">archives</Link>
-        <Link to="/settings">settings</Link>
+        <Link to="/archives"><button class="btn btn-danger">archives</button></Link>
+        <Link to="/settings"><button class="btn btn-success">settings</button></Link>
       </div>
     );
   }
 }

通过将button元素的class设置为”class = btn btn-danger”,可以应用Bootstrap的CSS并显示带装饰的按钮。
然而,实际上”class”这个关键词是JavaScript的保留字,所以不能在JavaScript源代码中使用这个关键词。
事实上,在JSX中,我们使用的是className来表示HTML的class属性,而不是class。

然而,如果在JSX中不能使用class关键字,那么在JavaScript中使用类似HTML标签的JSX的优点就变得微乎其微了……如果你持有这样的观点,那么可以使用babel-plugin-react-html-attrs来使JSX中也能使用class关键字。

$ npm install --save-dev babel-plugin-react-html-attrs
 var debug   = process.env.NODE_ENV !== "production";
 var webpack = require('webpack');
 var path    = require('path');
 
 module.exports = {
   context: path.join(__dirname, "src"),
   entry: "./js/client.js",
   module: {
     rules: [{
       test: /\.jsx?$/,
       exclude: /(node_modules|bower_components)/,
       use: [{
         loader: 'babel-loader',
         options: {
+          plugins: ['react-html-attrs'],
           presets: ['@babel/preset-react', '@babel/preset-env']
         }
       }]
     }]
   },
   output: {
     path: __dirname + "/src/",
     filename: "client.min.js"
   },
   plugins: debug ? [] : [
     new webpack.optimize.OccurrenceOrderPlugin(),
     new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
   ],
 };

请在这里重新运行npm start命令。
然后,请再次在Web浏览器中验证http://localhost:8080。

React_ReactRouter0003.png

您能确认按钮是否已用Bootstrap的CSS进行装饰吗?
使用JSX,您可以像HTML一样使用class属性(如果没有babel-plugin-react-html-attrs,则使用className属性),来指定HTML的class属性。

補充:其他class屬性的指定方法

在上述的源代码中,我们创建了一个button元素,并在其中添加了class属性,但是在react-router-dom的Link组件中添加class属性也可以做到类似的事情。

 import React from "react";
 import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives">archives</Link>
-        <Link to="/settings">settings</Link>
+        <Link to="/archives" class="btn btn-danger">archives</Link>
+        <Link to="/settings" class="btn btn-success">settings</Link>
       </div>
     );
   }
 }

当您在Web浏览器中查看http://localhost:8080时,您将看到与先前相同的装饰按钮被显示出来。

导航

在React Router中,当我们在普通的使用方式下按下每个按钮并且路径改变时,显示的状态也会在浏览器的历史记录中保存。因此,当我们按下某个按钮然后按下浏览器的返回按钮时,可以确认返回到前一个状态。由于我们正在构建的应用是单页面应用程序(SPA),没有页面跳转,所以按下返回按钮后,不会返回到应用程序之前的页面(在显示http://localhost:8080之前显示的网站)。

让我们使用 navigate 来稍微灵活地定制一下此历史管理器。
但要使用 react-router v4 并通过 navigate 来管理历史,需要使用 withRouter。
因此,为了避免过于复杂化,除非有特殊原因,最好还是使用默认设置。

    • this.props.history の関数一覧

this.props.history.push 画面遷移する。前居た画面を履歴に追加し、ブラウザの戻るボタンで戻れるようにする(React Router で標準的な挙動)。

this.props.history.replace 画面遷移する。前居た画面を置換するため、ブラウザの戻るボタンで戻れない。

假设我们使用this.props.history.push,那么代码如下所示。

 import React from "react";
-import { Link } from "react-router-dom";
+import { Link, withRouter } from "react-router-dom";
 
-export default class Layout extends React.Component {
+class Layout extends React.Component {
+  navigate() {
+    console.log(this.props.history);
+    this.props.history.push("/");
+  }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
+        <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
-
+export default withRouter(Layout);
React_ReactRouter0004.gif

当查看网页时…

点击「存档」 -> 点击「精选」 -> 点击「设置」 -> 点击返回按钮(返回到「精选」) -> 点击返回按钮(返回到「存档」)-> 点击返回按钮(返回到初始页面),这样就确认了 push 和 pop 到历史记录中。
接下来,让我们尝试使用 this.props.history.replace() 实现不留下浏览记录的转场。

 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
-    this.props.history.push("/");
+    this.props.history.replace("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
 
export default withRouter(Layout);
React_ReactRouter0004_02.gif

如果按照上述方法操作,可以看到当按下featured按钮时,显示的画面(设置)不会被记录在历史记录中。请注意,这并不意味着featured画面的历史记录消失了。
因此,如果您不想在单击按钮时将其保留在浏览器的历史记录中,请使用this.props.history.replace(“/”);。

顺便提一下,在这里进行的状态更改,请确保在进行下一课之前将其恢复到原来的状态。

 import React from "react";
-import { Link } from "react-router-dom";
+import { Link, withRouter } from "react-router-dom";
 
-export default class Layout extends React.Component {
+class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
-    this.props.history.replace("/");
+    this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
 
+export default withRouter(Layout);

获取URL的参数

我们将学习通过使用Get参数作为最简单的方法来传递信息到URL,并从URL参数中获取信息。

如果输入的URL是http://localhost:8080/archives/some-article,想要获取”some-article”部分,可以用:变量名的形式进行获取。
此外,由于path=”/archives”与path=”/archives/:article”部分匹配,因此访问/archives/foo位置时,两个组件都会被渲染。
因此,我们将在较短的path=”/archives”上添加exact关键字,以仅在完全匹配时进行渲染。

 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
 import Featured from "./pages/Featured";
 import Archives from "./pages/Archives";
 import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
 ReactDOM.render(
   <Router>
     <Layout>
       <Route exact path="/" component={Featured}></Route>
-      <Route path="/archives" component={Archives}></Route>
+      <Route exact path="/archives" component={Archives}></Route>
+      <Route path="/archives/:article" component={Archives}></Route>
       <Route path="/settings" component={Settings}></Route>
     </Layout>
   </Router>,
 app);

通过将中的Get参数的值设置为此值,可以将/archives之后的值传递到Archives组件中。

在上述的示例中,可以通过在Archives组件内使用this.props.match.params.article来获取参数(如果在Archives组件中不清楚参数是使用什么参数名传递的,可以使用console.log(this.props);来查看传递的参数列表进行确认)。

 import React from "react";
 
 export default class Archives extends React.Component {
   render() {
     return (
-      <h1>Archives</h1>
+      <h1>Archives ({this.props.match.params.article})</h1>
     );
   }
 }

在Layout.js中添加链接。

 import React from "react";
 import { Link, withRouter } from "react-router-dom";

 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
+        <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);

请使用Web浏览器打开http://localhost:8080,并点击”archives”(一些其他文章)和”archives”链接进行尝试。
当点击第一个链接时,页面跳转至http://localhost:8080/archives/some-other-articles,并在页面上显示参数(some-other-articles);
当点击第二个链接时,页面跳转至http://localhost:8080/archives,并显示空参数()。

React_ReactRouter0005.gif

使用正则表达式指定URL参数

可以使用正则表达式指定要传递给子组件的参数名。例如,下面的例子可以定义一个组件,在路径值为”/settings/main”或”/settings/extra”且变量名为this.props.match.params.mode时才会生效的Settings组件。

 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
 import Featured from "./pages/Featured";
 import Archives from "./pages/Archives";
 import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
 ReactDOM.render(
   <Router>
     <Layout>
       <Route exact path="/" component={Featured}></Route>
       <Route path="/archives/:article" component={Archives}></Route>
-      <Route path="/settings" component={Settings}></Route>
+      <Route path="/settings/:mode(main|extra)" component={Settings}></Route>
     </Layout>
   </Router>,
 app);

根据以上定义,可以在Settings组件中通过this.props.match.params.mode来引用值。

 import React from "react";
 
 export default class Settings extends React.Component {
   render() {
+    const type = (this.props.match.params.mode == "extra"? " (for experts)": "");
     return (
-      <h1>Settings</h1>
+      <h1>Settings{type}</h1>
     );
   }
 }

在 “设置/主要” 和 “设置/额外” 添加按钮。

 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives" class="btn btn-danger">archives</Link>
-        <Link to="/settings" class="btn btn-success">settings</Link>
+        <Link to="/settings/main" class="btn btn-success">settings</Link>
+        <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);

打开页面,然后点击设置、设置(额外)按钮试试看。

React_ReactRouter0007.gif

根据前面所述, 可以看出 this.props.match.param.mode 接收到了 main 和 extra 两个值。
另外,在 client.js 中使用了 的写法,但如果传递的值既不是 main 也不是 extra,则不会渲染 Settings 组件。

获取查询字符串

以下是有关如何在React Router中处理查询字符串的介绍。

当我们在浏览器中输入URL http://localhost:8080/archives?date=today&filter=hot 时,查询字符串指的是作为参数传递的?key=value中的值。

然而,据我了解,React Router v4 已经删除了解析这个查询字符串的方法。

    https://github.com/ReactTraining/react-router/issues/4410

因此,从React Router v4开始,您需要自行解析查询字符串,但在这里我们将介绍使用URLSearchParams的方法。
首先,在主页上添加一个包含查询字符串的链接按钮。

 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
-        <Link to="/archives" class="btn btn-danger">archives</Link>
+        <Link to="/archives/some-other-articles?date=yesterday&filter=none" class="btn btn-warning">archives (some other articles)</Link>
+        <Link to="/archives?date=today&filter=hot" class="btn btn-danger">archives</Link>
         <Link to="/settings/main" class="btn btn-success">settings</Link>
         <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);

然后,我们将解析存档组件的查询字符串,并将结果显示在屏幕上。

 import React from "react";
 
 export default class Archives extends React.Component {
   render() {
+    const query = new URLSearchParams(this.props.location.search)
+    let message
+        = (this.props.match.params.article ? this.props.match.params.article + ", ": "")
+        + "date=" + query.get("date") + ", filter=" + query.get("filter");
     return (
-      <h1>Archives ({this.props.match.params.article})</h1>
+      <h1>Archives ({message})</h1>
     );
   }
 }
React_ReactRouter0009.gif

使用React Router v4之后,仍然可以通过URLSearchParams方法轻松获取查询参数。

备注:如果浏览器不支持URLSearchParams

当浏览器不支持URLSearchParams时

如果浏览器不支持URLSearchParams,则还可以使用query-string库来解析。详细说明略。

当按钮处于激活状态时,指定class为activeClassName。

使用 NavLink 组件时,可以使用 activeClassName 属性来指定在相应的链接显示时应用的 HTML 类值。

 import React from "react";
-import { Link, withRouter } from "react-router-dom";
+import { NavLink, Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives/some-other-articles?date=yesterday&filter=none" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives?date=today&filter=hot" class="btn btn-danger">archives</Link>
-        <Link to="/settings/main" class="btn btn-success">settings</Link>
+        <NavLink to="/settings/main" class="btn btn-success" activeClassName="btn-danger">settings</NavLink>
         <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);
React_ReactRouter0011.gif

大致上,我們已經解釋了以上關於React Router的基本功能。
下一步將嵌入靜態HTML頁面到React的JSX中,完成應用程式的建立。

我已经将至此的源代码存储在以下代码库中,希望对您有所帮助。

    https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/01_active_class_name

附注:关于包含查询字符串的情况下activeClassName的行为

如果 NavLink 的 to 属性包含查询字符串,则 activeClassName 指定的 class 不会正确反映。这不是一个错误,而是因为从 React Router v4 开始,移除了对查询字符串的功能。

    https://github.com/ReactTraining/react-router/issues/5379

将HTML静态内容组件化

我们将把 src/index.html 中的静态内容移动到各个 React 组件中。
由于本次操作步骤繁琐,我们将只关注注重点,根据现有的内容逐步进行。

    https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/02_migrate_html_to_react/react_router

移行后,src/index.html 中的内容变得更加清晰。我们将在以下的

中以组件化的形式展示和呈现数据。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>React</title>
  <!-- Bootstrap Core CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cerulean/bootstrap.min.css" rel="stylesheet">

  <!-- Custom Fonts -->
  <!-- <link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
  <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
</head>

<body>
  <div id="app"></div>
  <script src="/client.min.js"></script>
</body>

</html>

在src/js/components/Article.js中,设置文章模板,并在src/js/pages/Featured.js中设置标题,以显示每篇文章。

import React from "react";

export default class Article extends React.Component {
  render() {
    const { title } = this.props;

    return (
      <div class="col-md-4">
        <h4>{title}</h4>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
    );
  }
}
import React from "react";
import Article from "../components/Article";

export default class Featured extends React.Component {
  render() {
    const Articles = [
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More",
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More",
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More"
    ].map((title, i) => <Article key={i} title={title} />);

    const adText = [
      "Ad spot #1",
      "Ad spot #2",
      "Ad spot #3",
      "Ad spot #4",
      "Ad spot #5"
    ];

    const randomAd = adText[Math.round(Math.random() * (adText.length - 1))];
    console.log("featured");

    return (
      <div>
        <div class="row">
          <div class="col-lg-12">
            <div class="well text-center">
              {randomAd}
            </div>
          </div>
        </div>
        <div class="row">{Articles}</div>
      </div>
    );
  }
}

在src/js/components/layout/Nav.js中,通过获取当前显示的位置信息,为页面上部的菜单设置仅活动状态的样式。

import React from "react";
import { Link } from "react-router-dom";

export default class Nav extends React.Component {
  constructor() {
    super();
    this.state = {
      collapsed: true
    };
  }
  toggleCollapse() {
    const collapsed = !this.state.collapsed;
    this.setState({collapsed});
  }
  render() {
    const { location } = this.props;
    const { collapsed } = this.state;
    const featuredClass = location.pathname === "/" ? "active" : "";
    const archivesClass = location.pathname.match(/^\/archives/) ? "active" : "";
    const settingsClass = location.pathname.match(/^\/settings/) ? "active" : "";
    const navClass = collapsed ? "collapse" : "";
    return (
      <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle" onClick={this.toggleCollapse.bind(this)}>
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
          </div>
          <div class={"navbar-collapse " + navClass} id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
              <li class={featuredClass}>
                <Link to="/" onClick={this.toggleCollapse.bind(this)}>Featured</Link>
              </li>
              <li class={archivesClass}>
                <Link to="/archives" onClick={this.toggleCollapse.bind(this)}>Archives</Link>
              </li>
              <li class={settingsClass}>
                <Link to="/settings" onClick={this.toggleCollapse.bind(this)}>Settings</Link>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    );
  }
}
React_ReactRouter0012.gif

React Router v3 的主要变更内容如下:

最後,我把我在使用React Router v3并最近开始使用React Router v4时遇到的各种变化总结在下面的网址文章中。希望对一直使用React Router v3的人有所帮助。

请拿以下内容作为参考。

REACT JS TUTORIAL #7 – React Router Params & Queries

Start Bootstrap

https://startbootstrap.com

BootstrapCDN

https://www.bootstrapcdn.com/bootswatch/

react router v^4.0.0 Uncaught TypeError: Cannot read property ‘location’ of undefined

https://stackoverflow.com/questions/42892488/react-router-v4-0-0-uncaught-typeerror-cannot-read-property-location-of-unde

Using React IndexRoute in react-router v4

https://stackoverflow.com/questions/42748727/using-react-indexroute-in-react-router-v4

Nested Routes in React Router v4

https://stackoverflow.com/questions/42095600/nested-routes-in-react-router-v4

Warning: Unknown DOM property class. Did you mean className?

https://stackoverflow.com/questions/30968113/warning-unknown-dom-property-class-did-you-mean-classname

The prop ‘history’ is marked as required in ‘Router’, but its value is ‘undefined’. in Router

https://stackoverflow.com/questions/43008036/the-prop-history-is-marked-as-required-in-router-but-its-value-is-undefine

React Router failed prop ‘history’, is undefined

https://stackoverflow.com/questions/42845303/react-router-failed-prop-history-is-undefined

React-router v4 this.props.history.push(…) not working

https://stackoverflow.com/questions/44312437/react-router-v4-this-props-history-push-not-working/44312810

How to push to History in React Router v4?

https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4

Migrating from v2/v3 to v4

https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md

广告
将在 10 秒后关闭
bannerAds