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 © 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 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”组件将会交替显示出来。
试试用按钮装饰链接
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。
您能确认按钮是否已用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);
当查看网页时…
点击「存档」 -> 点击「精选」 -> 点击「设置」 -> 点击返回按钮(返回到「精选」) -> 点击返回按钮(返回到「存档」)-> 点击返回按钮(返回到初始页面),这样就确认了 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);
如果按照上述方法操作,可以看到当按下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,并显示空参数()。
使用正则表达式指定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);
打开页面,然后点击设置、设置(额外)按钮试试看。
根据前面所述, 可以看出 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 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 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 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