npm install react-router-dom
路由路径是匹配一个(或一部分)URL 的 一个字符串模式。大部分的路由路径都可以直接按照字面量理解,除了以下几个特殊的符号:
:paramName
– 匹配一段位于 /
、?
或 #
之后的 URL。 命中的部分将被作为一个参数
()
– 在它内部的内容被认为是可选的*
– 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat
参数
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
如果一个路由使用了相对路径,那么完整的路径将由它的所有祖先节点的路径和自身指定的相对路径拼接而成。使用绝对路径可以使路由匹配行为忽略嵌套关系。
hash模式和history模式为什么页面不会刷新
1.hash模式
hash 通过监听浏览器 onhashchange 事件变化,查找对应路由应用。通过改变 location.hash 改变页面路由。
http://www.test.com/#/就是 Hash URL,当#后面的哈希值发生变化时,可以通过hashchange事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,都不向服务器端请求,服务器端接收到的 URL 请求永远是http://www.test.com。Hash 模式相对来说更简单,并且兼容性也更好。每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。
2.history模式
History模式是HTML5 新推出的功能,主要使用history.pushState和history.replaceState改变 URL。通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。
可通过 back、forward、go 等,可以读取历览器历史记录栈的信息,pushState、repalceState 还可以对浏览器历史记录栈进行修改。
history模式下(因为使用了 History API),url变化不会向服务器发送请求,但是只要页面刷新就会向浏览器发请求(此时页面的所有资源被清空,会向服务器请求重新获取资源),
发起请求后,服务器收到请求,根据自己配置的路由规则处理,若没有对应的路由,就会返回404给前端浏览器,因此需要服务端配置为未知路径提供 index.html
React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location
对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常用的 history 有三种形式, 但是你也可以使用 React Router 实现自定义的 history。
browserHistory
hashHistory
createMemoryHistory
你可以从 React Router 中引入它们:
// JavaScript 模块导入(译者注:ES6 形式)
import { browserHistory } from 'react-router'
然后将它们传递给<Router>
:
render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API (HTML5 history 模式)用于处理 URL,创建一个像example.com/some/path
这样真实的 URL 。
需要服务器配置(了解)
这种模式下,服务器需要做好处理 URL 的准备。处理应用启动最初的 /
这样的请求应该没问题,但当用户来回跳转并在 /accounts/123
刷新时,服务器就会收到来自 /accounts/123
的请求,这时你需要处理这个 URL 并在响应中包含 JavaScript 应用代码。
一个 express 的应用可能看起来像这样的:
const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()
// 通常用于加载静态资源
app.use(express.static(__dirname + '/public'))
// 在你应用 JavaScript 文件中包含了一个 script 标签
// 的 index.html 中处理任何一个 route
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})
app.listen(port)
console.log("server started on port " + port)
如果你的服务器是 nginx,请使用 try_files
指令:
server {
...
location / {
try_files $uri /index.html
}
}
当在服务器上找不到其他文件时,这可以让 nginx 服务器提供静态文件服务并指向index.html
文件。
对于Apache服务器也有类似的方式,创建一个.htaccess
文件在你的文件根目录下:
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
IE8, IE9 支持情况(了解)
如果我们能使用浏览器自带的 window.history
API,那么我们的特性就可以被浏览器所检测到。如果不能,那么任何调用跳转的应用就会导致 全页面刷新,它允许在构建应用和更新浏览器时会有一个更好的用户体验,但仍然支持的是旧版的。
你可能会想为什么我们不后退到 hash history,问题是这些 URL 是不确定的。如果一个访客在 hash history 和 browser history 上共享一个 URL,然后他们也共享同一个后退功能,最后我们会以产生笛卡尔积数量级的、无限多的 URL 而崩溃。
hash 通过监听浏览器 onhashchange 事件变化,查找对应路由应用。通过改变 location.hash 改变页面路由。
Hash history 使用 URL 中的 hash(#
)部分去创建形如 example.com/#/some/path
的路由。
window.location.hash = 'product' // 设置 url 的 hash,会在当前url后加上 '#product'
console.log(window.location.hash) // '#product'
// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener('hashchange', function(){
})
// 或者在body上加上onhashchange
<body onhashchange="myFunction()">
我应该使用 createHashHistory
吗?
Hash history 不需要服务器任何配置就可以运行,如果你刚刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个 web 应用都应该渴望使用 browserHistory
。
像这样 ?_k=ckuvup
没用的在 URL 中是什么?
当一个 history 通过应用程序的 push
或 replace
跳转时,它可以在新的 location 中存储 “location state” 而不显示在 URL 中,这就像是在一个 HTML 中 post 的表单数据。
在 DOM API 中,这些 hash history 通过 window.location.hash = newHash
很简单地被用于跳转,且不用存储它们的location state。但我们想全部的 history 都能够使用location state,因此我们要为每一个 location 创建一个唯一的 key,并把它们的状态存储在 session storage 中。当访客点击“后退”和“前进”时,我们就会有一个机制去恢复这些 location state。
Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他非DOM的渲染环境(像 React Native )。
和另外两种history的一点不同是你必须创建它,这种方式便于测试。
const history = createMemoryHistory(location)
import React from 'react'
import { render } from 'react-dom'
import { browserHistory, Router, Route, IndexRoute } from 'react-router'
import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features'
render(
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route path='features' component={Features} />
</Route>
</Router>,
document.getElementById('app')
)
https://blog.csdn.net/u013205165/article/details/93738974
1 hash 模式下,仅 # 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
地址栏里 #
以及后面部分都是不会随请求发送到服务器。
2 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:”不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。“
3 结合自身例子,对于一般的 Vue + Vue-Router + Webpack + XXX 形式的 Web 开发场景,用 history 模式即可,只需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。
https://www.jianshu.com/p/03840824ec70
https://www.cnblogs.com/bydzhangxiaowei/p/12238776.html
https://github.com/wwlh200/react-router-demo
https://react.docschina.org/docs/code-splitting.html#import
https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
create-react-app文档
React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。
只能导入 ES6 export default 导出的模块,不支持服务端渲染。
React.lazy
和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库。
const Component = React.lazy(() => import('./Component'));
此代码将会在组件首次渲染时,自动导入包含 Component
组件的包。
React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该 Promise 需要 resolve 一个 default
export 的 React 组件。
然后应在 Suspense
组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
fallback
属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense
组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense
组件包裹多个懒加载组件。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
使用示例:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
通常需要搭配错误边界使用,当加载失败使用降级后的UI
https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
只能导入 ES6 export default 导出的模块
使用之前:
import { add } from './math';
console.log(add(16, 26));
使用之后:
// math.js 导出 math对象
import("./math").then(math => {
console.log(math.add(16, 26));
});
当 Webpack 解析到该语法时,会自动进行代码分割。如果你使用 Create React App,该功能已开箱即用,你可以立刻使用该特性。Next.js 也已支持该特性而无需进行配置。
const Foo = () => import('./Foo')
把组件按组分块
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
如果你自己配置 Webpack,你可能要阅读下 Webpack 关于代码分割的指南。你的 Webpack 配置应该类似于此。
当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 @babel/plugin-syntax-dynamic-import 插件。
NOTE: This plugin is included in
@babel/preset-env
npm install --save-dev @babel/preset-env
package.json
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
}
// 第一步
npm install @loadable/component
// 第二步
const Loading = () => <div>Loading...</div>
const Home = loadable(
() => import('./Home'),
{LoadingComponent: Loading}
)
缺点:需要引入第三方包,不建议使用,已经弃用
Loadable( )
接收一个配置对象为参数,第一个属性名为loader
,是一个方法,用于动态加载我们所需要的模块,第二个参数就是我们的Loading
组件咯,在动态加载还未完成的过程中会有该组件占位。
{
loader: () => import('../containers/Home'),
loading: MyLoadingComponent
}
这个方法的返回值是一个react component,我们Route
组件和url香匹配时,加载的就是这个component,该component通过loader方法进行异步加载以及错误处理:() => import('../containers/Home').catch(err=>err)
基本用法:
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
// react-loadable便是利用了import()来进行动态加载
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
封装(高阶组件):
// utils.js
import Loadable from "react-loadable";
export default function asyncComponent(comp) {
return Loadable({
loader: comp, // () => import('./my-component') 的形式
loading: (props) => {
return "加载中...";
},
});
}
// 其它js文件
import asyncComponent from "../utils/utils";
const Component = asyncComponent(() => import('./Component'));
缺点:需要引入第三方包,该方法不建议使用,StrictMode下回报如下警告:
The old API will be supported in all 16.x releases, but applications using it should migrate to the new version.
该方法不建议使用
require.ensure(
dependencies: String[],
callback: function(require),
errorCallback: function(error),
chunkName: String
)
给定 dependencies
参数,将其对应的文件拆分到一个单独的 bundle 中,此 bundle 会被异步加载。**当使用 CommonJS 模块语法时,这是动态加载依赖项的唯一方法。**这意味着,可以在模块执行时才允许代码,只有在满足特定条件时才会加载 dependencies
。
dependencies
:字符串数组,声明 callback
回调函数中所需要的所有模块。callback
:当依赖项加载完成后,webpack 将会执行此函数,require
函数作为参数传入此函数中。当程序运行需要依赖时,可以使用 require()
来加载依赖。函数体可以使用此参数,来进一步执行 require()
模块。errorCallback
:当 webpack 加载依赖失败时会执行此函数。chunkName
:由 require.ensure
创建的 chunk 的名称。通过将相同 chunkName
传递给不同的 require.ensure
调用,我们可以将其代码合并到一个单独的 chunk 中,从而只产生一个浏览器必须加载的 bundle。Route 可以定义 getChildRoutes
,getIndexRoute
和 getComponents
这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。
如果配合 webpack 这类的代码分拆工具使用的话,一个原本繁琐的构架就会变得更简洁明了。
const CourseRoute = {
path: 'course/:courseId',
getChildRoutes(location, callback) {
require.ensure([], function (require) {
// 如果你是使用 es6 的写法,也就是你的组件都是通过 export default 导出的,那么需要加入.default。如果你是使用 CommonJS 的写法,也就是通过 module.exports 导出的,那就无须加 .default 了。
// 例如 require('./routes/Announcements').default
callback(null, [
require('./routes/Announcements'),
require('./routes/Assignments'),
require('./routes/Grades'),
])
})
},
getIndexRoute(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Index'))
})
},
getComponents(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Course'))
})
}
}
所有路由器组件的通用底层接口。通常应用程序将使用一个高级路由器代替:
使用底层的最常见用例是将自定义历史记录与状态管理库(例如Redux或Mobx)进行同步。 请注意,不需要将状态管理库与React Router一起使用,它仅用于深度集成。
import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
ReactDOM.render(
<Router history={history}>
<App />
</Router>,
node
);
要渲染的子元素
<Router>
<App />
</Router>
用于导航的 history 对象,由 history
包提供。
import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
const customHistory = createBrowserHistory();
ReactDOM.render(<Router history={customHistory} />, node);
当路由匹配到时,也有可能会抛出错误,此时你就可以捕获和处理这些错误。通常,它们会来自那些异步的特性,如 route.getComponents
,route.getIndexRoute
,和 route.getChildRoutes
。
当 URL 改变时,需要更新路由的 state 时会被调用。
它使用浏览器中的 History API (HTML5 history 模式)用于处理 URL,创建一个像example.com/some/path
这样真实的 URL 。
history API
是 H5
提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL
地址而不重新发起请求。
// 引入BrowserRouter这个组件的类型(接口)
import { BrowserRouterProps} from 'react-router-dom'
import {BrowserRouter as Router} from "react-router-dom";
<BrowserRouter
basename={optionalString}
forceRefresh={optionalBool}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber}
>
<App />
</BrowserRouter>
对于 history
来说,主要有以下特点:
url
可以是与当前 url
同源的任意 url
,也可以是与当前 url
一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中。history.state
,添加任意类型的数据到记录中。title
属性,以便后续使用。pushState
、 replaceState
来实现无刷新跳转的功能。对于 history
来说,确实解决了不少 hash
存在的问题,但是也带来了新的问题。具体如下:
history
模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求。如果 nginx
没有匹配得到当前的 url
,就会出现 404
的页面。hash
模式来说, 它虽然看着是改变了 url
,但不会被包括在 http
请求中。所以,它算是被用来指导浏览器的动作,并不影响服务器端。因此,改变 hash
并没有真正地改变 url
,所以页面路径还是之前的路径, nginx
也就不会拦截。history
模式时,需要通过服务端来允许地址可访问,如果没有设置,就很容易导致出现 404
的局面。所有locations的基本URL。如果应用程序是从服务器上的子目录提供的,则需要将其设置为子目录。格式正确的基名称应该有一个前导斜杠,但不能有尾随斜杠。
<BrowserRouter basename="/calendar">
<Link to="/today"/> // renders <a href="/calendar/today">
<Link to="/tomorrow"/> // renders <a href="/calendar/tomorrow">
...
</BrowserRouter>
用于确认导航的函数。默认为使用window.confirm。
Window.confirm()
方法显示一个具有一个可选消息和两个按钮(确定和取消)的模态对话框 。
let result = window.confirm(message);
<BrowserRouter
getUserConfirmation={(message, callback) => {
// this is the default behavior
const allowTransition = window.confirm(message);
callback(allowTransition);
}}
/>
如果为真,路由器将在页面导航上使用整页刷新。您可能希望使用它来模拟传统服务器呈现的应用程序在页面导航之间进行整页刷新的方式。
<BrowserRouter forceRefresh={true} />
location.key的长度,默认为6。
<BrowserRouter keyLength={12} />
渲染的子元素。若 React 版本小于16:渲染多个子元素时必须用一个根元素包裹。
hash
永远不会提交到 server
端(可以理解为只在前端自生自灭)。
Hash history 使用 URL 中的 hash(#
)部分去创建形如 example.com/#/some/path
的路由。
Hash history 不支持 location.key
or location.state
,但它的兼容性更好,不需要配置服务器,通常适用于老版本的浏览器。
hash
可以改变 url
,但是不会触发页面重新加载(hash的改变是记录在 window.history
中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 http
请求,所以这种模式不利于 SEO
优化。hash
只能修改 #
后面的部分,所以只能跳转到与当前 url
同文档的 url
。hash
通过 window.onhashchange
的方式,来监听 hash
的改变,借此实现无刷新跳转的功能。hash
永远不会提交到 server
端(可以理解为只在前端自生自灭)。所有locations的基本URL。如果应用程序是从服务器上的子目录提供的,则需要将其设置为子目录。格式正确的基名称应该有一个前导斜杠,但不能有尾随斜杠。
用于确认导航的函数。默认为使用window.confirm。
Window.confirm()
方法显示一个具有一个可选消息和两个按钮(确定和取消)的模态对话框 。
用于window.location.hash的编码类型,有效值为:
"slash"
- Creates hashes like #/
and #/sunshine/lollipops
,默认值为 slash
"noslash"
- Creates hashes like #
and #sunshine/lollipops
"hashbang"
- Creates “ajax crawlable” (被谷歌反对) hashes like #!/
and #!/sunshine/lollipops
slash—斜线
单个被渲染的子元素。
它将您的“URL”的历史记录保存在内存中(不读取、不写入地址栏)。在测试和非浏览器环境(如React Native)中非常有用。
<MemoryRouter
initialEntries={optionalArray}
initialIndex={optionalNumber}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber}
>
<App />
</MemoryRouter>
元素是history stack中location的数组。这些元素可能是具有{pathname,search,hash,state}对象或者是字符串url。
<MemoryRouter
initialEntries={["/one", "/two", { pathname: "/three" }]}
initialIndex={1}
>
<App />
</MemoryRouter>
initialEntries数组中初始位置的索引。
loaction.key 的长度,默认为6。
渲染的子元素。若 React 版本小于16:渲染多个子元素时必须用一个根元素包裹。
在应用程序周围提供声明性的、可访问的导航。<Link>
以适当的 href 去渲染一个可访问的锚标签。
<Link>
可以知道哪个 route 的链接是激活状态的,并可以自动为该链接添加 activeClassName
或 activeStyle
。
跳转链接的路径,字符串形式,如 /users/123
或/users?userId=123&age=18
。
跳转链接的对象,可以是具有以下任何属性的对象:
pathname
: A string representing the path to link to.search
: A string representation of query parameters.hash
: A hash to put in the URL, e.g. #a-hash
.state
: State to persist to the location
.<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { /* some key/value */ }
}}
/>
loaction 作为参数,应当返回以字符串形式或对象形式代表的 location。
<Link to={location => ({ ...location, pathname: "/courses" })} />
<Link to={location => `${location.pathname}?sort=name`} />
如果为true,则单击链接将替换 history stack中的当前location,而不是添加新的location。
<Link to="/courses" replace />
<Link to="/" component={myComponent} />
注意:React Router 目前还不能管理滚动条的位置,并且不会自动滚动到 hash 对应的元素上。如果需要管理滚动条位置,可以使用 scroll-behavior 这个库。
当某个 route 是激活状态时,<Link>
可以接收传入的 className。当元素处于活动状态时提供(增加)该元素的类名,默认值是active
。
当元素处于活动状态时提供(增加)给该元素的样式对象(采用小驼峰命名属性的 JavaScript 对象)。
自定义点击事件的处理方法。如处理 <a>
标签一样 - 调用 e.preventDefault()
来防止过度的点击,同时 e.stopPropagation()
可以阻止冒泡的事件。
如 <Route path="/users/:userId" />
这样的 route:
<Link to={`/users/${user.id}`} activeClassName="active">{user.name}</Link>
// 变成它们其中一个依赖在 History 上,当这个 route 是
// 激活状态的
<a href="/users/123" class="active">Michael</a> // browserHistory模式
<a href="#/users/123">Michael</a> // hashHistory模式
// 修改 activeClassName
<Link to={`/users/${user.id}`} activeClassName="current">{user.name}</Link>
// 当链接激活时,修改它的样式
<Link to="/users" style={{color: 'white'}} activeStyle={{color: 'red'}}>Users</Link>
当元素处于活动状态时提供(增加)给该元素的类名,默认值是active
。
当元素处于活动状态时提供(增加)给该元素的样式对象(采用小驼峰命名属性的 JavaScript 对象)。
如果为true,则仅当location完全匹配时才应用活动类/样式。
如果路由还有子路由,则该路由的 exact 需要设置为false
<NavLink exact to="/profile">
Profile
</NavLink>
如果为true,则在确定location是否与当前URL匹配时,将考虑 location 路径名上的尾部斜杠。
添加额外逻辑决定链接能够处于活动状态的函数,需要返回一个布尔值。
<NavLink
to="/events/123"
isActive={(match, location) => {
if (!match) {
return false;
}
// 奇数返回true,偶数返回false
const eventID = parseInt(match.params.eventID);
return !isNaN(eventID) && eventID % 2 === 1;
}}
>
Event 123
</NavLink>
location 对象包含有关当前 URL 的信息。形式大概就像这样:
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: 'something'
} // 仅在 browser history 和 memory history中有效
}
你使用以下几种方式来获取 location 对象:
this.props.location
的方式获取,({ location }) => ()
的方式获取,({ location }) => ()
的方式获取,this.props.location
的方式获取。aria-current属性应用在处于激活状态的链接,有效值为:
"page"
- 用于指示一组分页链接中的链接,默认值"step"
- 用于指示基于步骤的流程的步骤指示器内的链接"location"
- 用于指示可视高亮显示为流程图当前组件的图像"date"
- 用于指示日历中的当前日期"time"
- 用于指示时间表中的当前时间"true"
- 用于指示导航链接是否处于活动状态"false"
- 用于防止辅助技术对当前链接作出反应(一个用例是防止单个页面上出现多个aria当前标记)用于在离开页面之前提示用户。例如表单已填写一半,用户点击跳转到其它页面时,应该向用户呈现一个。
当用户试图离开时提示用户的消息
参数是将要跳转到的location和action(字符串:PUSH、REPLACE、POP),根据判断条件需要返回一个true或者提示信息。true则直接跳转,字符串则是用户离开时的提示信息。
<Prompt
message={(location, action) => {
if (action === 'POP') {
console.log("Backing up...")
}
return location.pathname.startsWith("/app")
? true
: `Are you sure you want to go to ${location.pathname}?`
}}
/>
在when={true}或when={false}时以相应地阻止(true)或允许(false)导航。
在应用中 <Redirect>
可以设置重定向到其他 route 而不改变旧的 URL。
新的location将覆盖history stack中的当前location。
<Route exact path="/">
{loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
重定向模板URL,from中所有URL参数必须在to中体现
<Redirect to="/somewhere/else"/>
重定向目标对象
<Redirect
to={{
pathname: "/login",
search: "?utm=your+face",
state: { }
}}
/>
你想由哪个路径进行重定向,包括动态段。
确保from中所有URL参数都在to中有使用。
<Switch>
<Redirect from="/old-path" to="/new-path" />
<Route path="/new-path">
<Place />
</Route>
</Switch>
// Redirect with matched parameters
<Switch>
<Redirect from="/users/:id" to="/users/profile/:id" />
<Route path="/users/profile/:id">
<Profile />
</Route>
</Switch>
没有 path 属性的< Route > 或者 没有 from 属性的 < Redirect > 将总是匹配到当前的地址(location),然后进行渲染。
设置为true时,重定向会将新 location 推送到 history stack 中,而不是替换当前 location
<Redirect push to="/somewhere/else" />
精确匹配路径。
如果路由还有子路由,则该路由的 exact 需要设置为false
当在内部渲染时,只能与from结合使用以精确匹配location。
path | location.pathname | exact | matches? |
---|---|---|---|
/one |
/one/two |
true |
no |
/one |
/one/two |
false |
yes |
如果为true,则在确定location是否与当前URL匹配时,将考虑 location 路径名上的尾部斜杠。
当在内渲染时,只能与from结合使用以严格匹配location。
如果为true,则匹配路径是否区分大小写。
path | location.pathname | sensitive | matches? |
---|---|---|---|
/one |
/one |
true |
yes |
/One |
/one |
true |
no |
/One |
/one |
false |
yes |
注意,在 route 层 <Redirect>
可以被放在任何地方,尽管正常的优先 规则仍适用。
<Route path="course/:courseId">
<Route path="dashboard" />
{/* /course/123/home -> /course/123/dashboard */}
<Redirect from="home" to="dashboard" />
</Route>
优先级
路由算法会根据定义的顺序自顶向下匹配路由。因此,当你拥有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的路径。例如,千万不要这么做:
<Route path="/comments" ... />
<Redirect from="/comments" to="/others" />
它最基本的职责是在某个UI的路径与当前URL匹配时呈现该UI。具有占位作用。
如果同一个组件被用作组件树中同一点上多个的子组件,React会将其视为同一个组件实例,并且组件的状态将在路由更改之间保留。如果不需要这样做,则添加到每个路由组件的唯一key
将导致React在路由更改时重新创建组件实例。
以上三种渲染方法都是传递一个包含以下三个路由参数的对象
当匹配到 URL 时,单个组件会被渲染。它可以被父 route 组件的 this.props.children
渲染。
const routes = (
<Route component={App}>
<Route path="groups" component={Groups}/>
<Route path="users" component={Users}/>
</Route>
)
class App extends React.Component {
render () {
return (
<div>
{/* 这会是 <Users> 或 <Groups> */}
{this.props.children}
</div>
)
}
}
可以使用 this.props.location
等获取路由参数。
当您使用组件(而不是下面的 render 或 children)时,路由器会使用React.createElement从给定的组件中创建一个新的React元素。 这意味着,如果您向组件prop提供内联函数,则将在每个渲染中创建一个新组件。 因为在每次渲染时,都会重新将一个新的函数赋值给组件,所以将导致现有组件的卸载和新组件的安装,而不仅仅是更新现有组件。 使用内联函数进行内联渲染时,请使用render或children。
Route 可以定义一个或多个已命名的组件,当路径匹配到 URL 时, 它们可以被 父 route 组件的 this.props[name]
渲染。
// 想想路由外部的 context — 如果你可拔插
// `render` 的部分,你可能需要这么做:
// <App main={<Users />} sidebar={<UsersSidebar />} />
const routes = (
<Route component={App}>
<Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}}/>
<Route path="users" components={{main: Users, sidebar: UsersSidebar}}>
<Route path="users/:userId" component={Profile}/>
</Route>
</Route>
)
class App extends React.Component {
render () {
const { main, sidebar } = this.props // 当路径匹配到 URL 时,可以被父route组件的 `this.props[name]` 访问
return (
<div>
<div className="Main">
{main}
</div>
<div className="Sidebar">
{sidebar}
</div>
</div>
)
}
}
class Users extends React.Component {
render () {
return (
<div>
{/* 当路径是 "/users/123" 是 `children` 会是 <Profile> */}
{/* UsersSidebar 也可以获取作为 this.props.children 的 <Profile> ,
所以这有点奇怪,但你可以决定哪一个可以
继续这种嵌套 */}
{this.props.children}
</div>
)
}
}
render可以方便地进行内联渲染和包装,而无需进行上述不必要的重新安装。
render函数可以传递路由参数routeProps:match、loaction、history。
<Route path="/home" render={(routeProps) => <div>Home</div>} />
与location匹配才渲染。
优先于,因此不要在同一个中同时使用这两个组件。
有时无论路径是否与location匹配都需要渲染。在这些情况下,可以使用函数children属性。它的工作原理与render完全相同,只是无论是否匹配都会调用它。
children函数可以传递路由参数routeProps:match、loaction、history。
这对于动画也很有用:
<Route
children={({ match, ...rest }) => (
{/* 动画将始终渲染 */}
<Animate>
{match && <Something {...rest}/>}
</Animate>
)}
/>
优先于和,因此不要在同一个中同时使用。
任何有效的URL路径(string)或路径数组(string [ ])
它会与父 route 的路径连接,除非它是从 /
开始的, 将它变成一个绝对路径。
注意:在动态路由中,绝对路径可能不适用于 route 配置中。
如果它是 undefined,路由会去匹配子 route。
<Route path="/users/:id">
<User />
</Route>
<Route path={["/users/:id", "/profile/:id"]}>
<User />
</Route>
没有 path 属性的< Route > 或者 没有 from 属性的 < Redirect > 将总是匹配到当前的地址(location),然后进行渲染。
精确匹配路径。
如果路由还有子路由,则该路由的 exact 需要设置为false
path | location.pathname | exact | matches? |
---|---|---|---|
/one |
/one/two |
true |
no |
/one |
/one/two |
false |
yes |
如果为true,则在确定location是否与当前URL匹配时,将考虑 location 路径名上的尾部斜杠。
当location.pathname中有其他URL段时,strict无效。
path | location.pathname | strict | matches? |
---|---|---|---|
/one/ |
/one |
true | no |
/one/ |
/one/ |
true | yes |
/one/ |
/one/two |
true | yes |
strict 可以用来强制 location.pathname 不能有尾部斜杠,但要做到这一点,strict 和 excat 都必须是真的。
location 对象包含有关当前 URL 的信息。形式大概就像这样:
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: 'something'
} // 仅在 browser history 和 memory history中有效
}
你使用以下几种方式来获取 location 对象:
this.props.location
的方式获取,({ location }) => ()
的方式获取,({ location }) => ()
的方式获取,this.props.location
的方式获取。如果元素包裹在中并与传递给的location(或当前history location)匹配,则传递给的 location 将被的 location 覆盖。
区分大小写。
path | location.pathname | sensitive | matches? |
---|---|---|---|
/one |
/one |
true |
yes |
/One |
/one |
true |
no |
/One |
/one |
false |
yes |
与component
属性 相比,它是异步的,能够实现按需加载,对于 code-splitting(代码分割)很有用。
cb(err, component)
<Route path="courses/:courseId" getComponent={(location, cb) => {
// 做一些异步操作去查找组件
cb(null, Course)
}}/>
当 route 即将进入时调用。它有三个参数:下一个路由的 state,重定向到另一个路径的方法replaceState,回调函数。this
会触发钩子去创建 route 实例。
replaceState(state,replacePath)
有两个参数:第一个参数用于更新 state 的 state 对象,第二个参数是重定向的路径。
当 callback
作为函数的第三个参数传入时,这个钩子将是异步执行的,并且跳转会阻塞直到 callback
被调用。
当 route 即将退出时调用。
在路由跳转过程中,
onLeave
hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter
hook会从最外层的父路由开始直到最下层子路由结束。
从不改变位置的。
这在服务器端渲染场景中非常有用,因为当用户没有实际单击时,所以location不会实际更改。它在简单的测试中也很有用:当您只需要插入一个位置并对输出进行断言时。
所有location的基本URL。格式正确的基名称应该有一个前导斜杠,但不能有尾随斜杠。
服务器收到的URL,可能是节点服务器上的req.url。
location对象:{pathname, search, hash, state}
纯JavaScript对象。在渲染期间,组件可以向对象添加属性以存储有关渲染的信息。
const context = {}
<StaticRouter context={context}>
<App />
</StaticRouter>
当匹配时,它将context对象传递给组件作为staticContext属性。有关如何自己执行此操作的详细信息,请参阅 Server Rendering guide 。
渲染后,这些属性可用于配置服务器的响应。
if (context.status === "404") {
// ...
}
渲染的子元素。若 React 版本小于16:渲染多个子元素时必须用一个根元素包裹。
渲染与location匹配的第一个子级或。
< Switch >的独特之处是独它仅仅渲染一个路由。嵌套路由也需要加。如果没有被包裹,每一个包含匹配地址(location)的< Route >都会被渲染。思考下面的代码:
import { Route } from "react-router";
let routes = (
<div>
<Route path="/about">
<About />
</Route>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</div>
);
如果现在的URL是 /about ,那么 < About >, < User >, 还有 < NoMatch > 都会被渲染,因为它们都与路径(path)匹配。这种设计,允许我们以多种方式将多个 < Route > 组合到我们的应用程序中,例如侧栏(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶尔我们只想选择一个< Route > 来渲染。如果我们现在处于 /about ,我们也不希望匹配 /:user (或者显示我们的 “404” 页面 )。以下是使用 Switch 的方法来实现:
import { Route, Switch } from "react-router";
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
现在,如果我们处于 /about, 将开始寻找匹配的 。将被匹配, 将停止寻找匹配并渲染。 同样,如果我们处于 /michael , 将被渲染。
这对于动画过渡也很有用,因为匹配的渲染位置与上一个相同:
let routes = (
<Fade>
<Switch>
{/* there will only ever be one child here */}
{/* 这里只会有一个子节点 */}
<Route />
<Route />
</Switch>
</Fade>
);
用于覆盖匹配子元素的location的location对象而不是当前history location(通常是当前浏览器URL)。
< Switch > 的所有子节点应为 < Route > 或 < Redirect > 元素。只有匹配当前地址(location)的第一个子节点才会被渲染。
< Route > 元素使用它们的 path 属性匹配,< Redirect > 元素使用它们的 from 属性匹配。没有 path 属性的< Route > 或者 没有 from 属性的 < Redirect > 将总是匹配到当前的地址(location),然后进行渲染。
如果有一个location属性,它将覆盖匹配到的子元素上的location。
generatePath函数可用于生成路由的URL。
内部使用regexp库的路径。
import { generatePath } from "react-router";
generatePath("/user/:id/:entity(posts|comments)", {
id: 1,
entity: "posts"
});
// Will return /user/1/posts
将路径编译为正则表达式的结果会被缓存。
generatePath接受2个参数pattern和params。
作为路径属性的路径模板。
该对象具有pattern(路径模板)中要使用的相应参数。
如果提供的参数和路径不匹配,将抛出错误:
generatePath("/user/:id/:entity(posts|comments)", { id: 1 });
// TypeError: Expected "entity" to be defined
本文档中的术语history
和history object
指的是history
包,它是React Router(包括React本身)仅有的两个主要依赖项之一。在不同的 Javascript 环境中,history 以多种形式实现了对于 session 历史的管理。
也使用以下术语:
本文档中的
history
是React Router的history
包,不是 window.history
history对象通常具有以下属性和方法:
history stack中条目的数量
表示history stack中发生的更改类型
location 对象包含有关当前 URL 的信息。形式大概就像这样:
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: 'something'
} // 仅在 browser history 和 memory history中有效
}
你使用以下几种方式来获取 location 对象:
this.props.history
的方式获取,({ history }) => ()
的方式获取,({ history }) => ()
的方式获取,this.props.history
的方式获取。将新的条目推送到history stack顶。
替换history stack顶部的条目。
在 history 中使用 n
或 -n
进行前进或后退
在 history 中后退,等于go(-1)
在 history 中前进,等于go(1)
离开时阻止页面跳转
history 对象是可变的(随着地址改变而改变),因此需要以this.props.location
和 ({ location }) => ()
的方式获取location,而不是通过 history.location获取。这能确保在生命周期内正确使用location对象。
class Comp extends React.Component {
componentDidUpdate(prevProps) {
// will be true
const locationChanged =
this.props.location !== prevProps.location;
// INCORRECT, will *always* be false because history is mutable.
const locationChanged =
this.props.history.location !== prevProps.history.location;
}
}
<Route component={Comp} />;
location 对象包含有关当前 URL 的信息。形式大概就像这样:
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: 'something'
} // 仅在 browser history 和 memory history中有效
}
你使用以下几种方式来获取 location 对象:
this.props.location
的方式获取,({ location }) => ()
的方式获取,({ location }) => ()
的方式获取,this.props.location
的方式获取。location 对象不会发生改变,因此你可以在生命周期的钩子函数中使用 location 对象来查看当前页面的位置是否发生改变,这种技巧在获取远程数据以及使用动画时非常有用。
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
// navigated!
}
}
你可以在不同环境中使用 location :
通常情况下,你只需要给一个字符串当做 location ,但是,当你需要添加一些 location 的state时,你可以使用 location 对象。并且当你需要多个 UI ,而这些 UI 取决于浏览历史时,例如弹出框(modal),使用location 对象会有很大帮助。
// usually all you need
<Link to="/somewhere"/>
// but you can use a location instead
const location = {
pathname: '/somewhere',
state: { fromDashboard: true }
}
<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)
最后,你可以把 location 传入以下组件:
这样做可以让组件不使用路由状态(router state)中的真实 location,因为我们有时候需要组件去渲染一个其他的 location 而不是本身所处的真实 location,比如使用动画或是等待跳转时。
match 对象包含了 如何与URL匹配的信息。
match 对象包含以下属性:
params
- (object) 路径参数对象,通过解析URL中动态的部分获得的键值对。isExact
- (boolean) 当为 true 时,整个URL都需要精确匹配(无尾随字符)。path
- (string) 用来做匹配的路径格式。用于构建嵌套的。url
- (string) URL匹配的部分,与location.pathname
相等。用于构建嵌套的。你可以在以下地方获取 match 对象:
this.props.match
({ match }) => ()
({ match }) => ()
this.props.match
如果没有path,则会与最接近的父级匹配。也是如此。
使用children属性的将调用其children函数,即使路由的路径与当前location不匹配,此时match为null。
“解析”URL的默认方法是将match.url字符串拼接到“相对”路径。
`${match.url}/relative-path`
如果在match为null时尝试执行此操作,最终会出现TypeError。 这意味着在使用children属性尝试连接< Route >内的“relative”路径是不安全的。
在match为null的内部使用无path时,会出现类似但更微妙的情况。
// location.pathname = '/matches'
<Route path="/does-not-match"
children={({ match }) => (
// match === null
// 无path
<Route
render={({ match: pathlessMatch }) => (
// pathlessMatch === ???
)}
/>
)}
/>
没有path属性的< Route >
从其父级继承匹配对象。 如果他们的父级路由的match为null,那么他们的match也将为null。 这意味着
a)任何子路由/链接必须是绝对路径,因为没有要解析的父级。
b)父级路由match为null时,无path的子路由将需要使用children属性来渲染。
这允许您使用与中相同的match,除了在正常生命周期之外,例如在服务器上渲染之前收集数据依赖关系。
import { matchPath } from "react-router";
const match = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});
第一参数是你想要匹配的 pathname, 如果你正在使用服务端的 nodos.js 下使用, 将会是 req.url。
第二个参数是要用于与match匹配的对象,它们与Route接受的属性相同。 其中path可以是字符串或字符串数组:
{
path, // like /users/:id; either a single string or an array of strings
strict, // optional, defaults to false
exact, // optional, defaults to false
sensitive, // optional, defaults to false
}
当props的path属性与match对象的path属性匹配时,它将返回一个对象。
matchPath("/users/2", {
path: "/users/:id",
exact: true,
strict: true
});
// {
// isExact: true
// params: {
// id: "2"
// }
// path: "/users/:id"
// url: "/users/2"
// }
当props的path属性与match对象的path属性不匹配时,它将返回null。
把不是通过路由切换过来的组件中,用 withRouter 包裹,将react-router 的 history、location、match 三个对象传入props对象上
默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push('/detail')跳转到对应路由的页面
然而不是所有组件都直接与路由相连(通过路由跳转到此组件)的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
使用场景:
比如app.js这个组件,一般是首页,不是通过路由跳转过来的,而是直接从浏览器中输入地址打开的,如果不使用withRouter此组件的this.props为空,没法执行props中的history、location、match等方法。
withRouter
只是用来处理数据更新问题的。在使用一些 redux 的connect()或者 mobx的inject()的组件中,如果依赖于路由的更新要重新渲染,会出现路由更新了但是组件没有重新渲染的情况。这是因为 redux 和 mobx 的这些连接方法会修改组件的shouldComponentUpdate。
所以在使用 withRouter
解决更新问题的时候,一定要保证 withRouter
在最外层,比如withRouter(connect()(Component))
,而不是 connect()(withRouter(Component))
。
组件(ShowTheLocation)的所有非React的静态方法和属性都会被自动的复制到已连接的组件(withRouter(ShowTheLocation))。
封装的组件作为组件上的静态属性WrappedComponent的值,这个组件可以用于在隔离环境中测试。
// MyComponent.js
export default withRouter(MyComponent)
// MyComponent.test.js
import MyComponent from './MyComponent'
render(<MyComponent.WrappedComponent location={{...}} ... />)
作为函数形式的ref传递给包装组件,参数是封装组件(Container)。
class Container extends React.Component {
componentDidMount() {
this.component.doSomething();
}
render() {
return (
<MyComponent wrappedComponentRef={c => (this.component = c)} />
);
}
}
<IndexLink>
写在组件内。
如果你使用 <Link to="/">Home</Link>
, 它会一直处于激活状态,因为所有的 URL 的开头都是 /
。
如果链接到根路由/,则不能使用Link组件,而要使用IndexLink组件。因为根路由的特殊性,/
会匹配任何子路由。使用IndexLink组件可以对路径进行精确匹配。
// 在上面的代码中,根路由只会在精确匹配时才具有activeClassName属性
<IndexLink to="/" activeClassName="active" onlyActiveOnIndex={true}>
Home
</IndexLink>
如果不想使用IndexLink组件,也可以使用Link组件中的onlyActiveOnIndex属性达到同样的效果。
<Link to="/" activeClassName="active" onlyActiveOnIndex={true}>
Home
</Link>
实际上,IndexLink组件就是对Link组件的onlyActiveOnIndex属性封装后的高阶组件。
当用户在父 route 的 URL 时, Index Routes 允许你为父 route 提供一个默认的 "child", 并且为使<IndexLink>
能用提供了约定。
与 Route 的 props 一样,除了 path
。
想象一下当 URL 为 /
时,我们想渲染一个在 App
中的组件。不过在此时,App
的 render
中的 this.props.children
还是 undefined
。这种情况我们可以使用 IndexRoute
来设置一个默认页面。
import React from 'react'
import { Router, Route, Link } from 'react-router'
import { IndexRoute } from 'react-router'
const Dashboard = React.createClass({
render() {
return <div>Welcome to the app!</div>
}
})
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{this.props.children}
</div>
)
}
})
React.render((
<Router>
<Route path="/" component={App}>
{/* 当 url 为/时渲染 Dashboard */}
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
现在看起来如下:
URL | 组件 |
---|---|
/ |
App -> Dashboard |
Index Redirects 允许你从一个父 route 的 URL 重定向到其他 route。 它们被用于允许子 route 作为父 route 的默认 route, 同时保持着不同的 URL。与 Redirect 的 props 一样,除了 from
。
路径前没有 /
如果我们可以将 /inbox
从 /inbox/messages/:id
中去除,并且还能够让 Message
嵌套在 App -> Inbox
中渲染,那会非常赞。绝对路径可以让我们做到这一点。
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
{/* 使用绝对路径 /messages/:id 替换 messages/:id */}
<Route path="/messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
在多层嵌套路由中使用绝对路径的能力让我们对 URL 拥有绝对的掌控。我们无需在 URL 中添加更多的层级,从而可以使用更简洁的 URL。
我们现在的 URL 对应关系如下:
URL | 组件 |
---|---|
/ |
App -> Dashboard |
/about |
App -> About |
/inbox |
App -> Inbox |
/messages/:id |
App -> Inbox -> Message |
提醒:绝对路径可能在动态路由中无法使用。
在 context 中给定路由的 state、设置 history 对象和当前的 location,<RoutingContext>
就会去渲染组件树。
目前我们是基于create-react-app脚手架搭建起来的项目简单配置的,直接上代码
npx create-react-app my-app --typescript
npm install --save react-router-dom @types/react-router-dom
declare module "react-router-dom";
// Layout.tsx
import * as React from "react";
import RouteView, { IRouteViewProps } from "../routes/RouteView";
import { History } from "history";
interface ILayoutProps extends IRouteViewProps {
history: History;
}
const Layout = (props: ILayoutProps) => {
const handleClick = React.useCallback((e) => {
const { name } = e.target;
props.history.push(name);
}, [props.history]);
return (
<div>
<div>
<button name="/basic/page1" onClick={handleClick}>
Page1
</button>
<button name="/basic/page2" onClick={handleClick}>
Page2
</button>
<button name="/basic/page3" onClick={handleClick}>
Page3
</button>
</div>
<RouteView {...props} />
</div>
);
};
export default Layout;
// Page1.tsx
import * as React from "react";
const Page1 = () => {
return (
<div>我是Page1</div>
)
};
export default Page1;
// Page2.tsx
import * as React from "react";
const Page2 = () => {
return (
<div>我是Page2</div>
)
};
export default Page2;
// Page3.tsx
import * as React from "react";
const Page3 = () => {
return (
<div>我是Page3</div>
)
};
export default Page3;
// router.config.ts
import Layout from "../pages/Layout";
import { lazy } from "react";
const routesConfig = [
{
path: "/basic",
component: Layout,
childrenRoutes: [
{
path: "/basic/page1",
component: lazy(() => import("../pages/Page1")),
},
{
path: "/basic/page2",
component: lazy(() => import("../pages/Page2")),
},
{
path: "/basic/page3",
component: lazy(() => import("../pages/Page3")),
},
{ path: "/basic", redirect: "/basic/page1" },
],
},
// {
// path: "/login",
// component: lazy(() => import("../pages/Login")),
// },
{
path: "/",
redirect: "/basic",
},
];
export default routesConfig;
// RouteView.tsx
import React from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'
import {connect} from 'react-redux'
export interface IRouteViewProps {
path?: string
redirect?: string
component?: any
childrenRoutes?: IRouteViewProps[]
}
// RouteView.tsx
import React from 'react'
import { Redirect, Route, Switch, RedirectProps } from 'react-router-dom'
export interface IRouteViewProps {
path?: string
redirect?: RedirectProps
component?: any
childrenRoutes?: IRouteViewProps[]
}
const RouteView = (props: IRouteViewProps) => {
return (
<Switch>
{redirect && <Redirect {...redirect} />}
<Route
path={props.path}
render={routeProps => {
return (
<props.component {...routeProps}>
{childrenRoutes && childrenRoutes.length > 0 && (
<Switch>
{childrenRoutes.map((route, index) => (
// 因为是RouteViewContainer,因此会自动传入isLogin
<RouteViewContainer {...route} key={index} />
))}
</Switch>
)}
</props.component>
) : null
}}
></Route>
<Switch/>
)
}
// 这里我们使用了redux给RouteView生成一个容器组件
// 如果不使用redux,也可以直接使用RouteView组件
const RouteViewContainer = withRouter(connect()(RouteViewContainer))
export default RouteViewContainer
// App.tsx
import React, { Suspense } from "react";
import routesConfig from "./routes/router.config";
import RouteViewContainer from "./routes/RouteView";
import { BrowserRouter } from "react-router-dom";
const App = () => {
return (
<BrowserRouter>
<Suspense fallback={<div>loading...</div>}>
<Switch>
<Redirect exact from="/" to="/login" />
{routesConfig.map((route,index)=>(
<RouteViewContainer {...route} key={index} />
))}
<Switch />
</Suspense>
</BrowserRouter>
);
};
export default App;
一个具备路由嵌套,路由懒加载,可配置化的的React-Router就配置好了。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
还可以使用 react-router-config 这个库。
route 定义的一个普通的 JavaScript 对象。 Router
把 JSX 的 <Route>
转化到这个对象中。 所有的 props 都和 <Route>
的 props 一样,除了以下属性。
示例:
因为 route 一般被嵌套使用,所以使用 JSX 这种天然具有简洁嵌套型语法的结构来描述它们的关系非常方便。然而,如果你不想使用 JSX,也可以直接使用原生 route 数组对象。
上面我们讨论的路由配置可以被写成下面这个样子:
const routeConfig = [
{ path: '/', // path: stirng | string[]
component: App,
// indexRedirect:'/about', // 重定向路由。不过一般使用 redirect组件进行重定向
childRoutes: [
{ path: 'about', component: About },
{ path: 'inbox',
component: Inbox,
childRoutes: [
{ path: '/messages/:id', component: Message },
{ path: 'messages/:id',
onEnter: function (nextState, replaceState) {
replaceState(null, '/messages/' + nextState.params.id)
}
}
]
}
]
}
]
React.render(<Router routes={routeConfig} />, document.body)
子 route 的一个数组,与在 JSX route 配置中的 children
一样。
与 childRoutes
一样,但是是异步的,并且可以接收 location
。对于 code-splitting 和动态路由匹配很有用(给定一些 state 或 session 数据会返回不同的子 route)。
callback
cb(err, routesArray)
let myRoute = {
path: 'course/:courseId',
childRoutes: [
announcementsRoute,
gradesRoute,
assignmentsRoute
]
}
// 异步的子 route
let myRoute = {
path: 'course/:courseId',
getChildRoutes(location, cb) {
// 做一些异步操作去查找子 route
cb(null, [ announcementsRoute, gradesRoute, assignmentsRoute ])
}
}
// 可以根据一些 state
// 跳转到依赖的子 route
<Link to="/picture/123" state={{ fromDashboard: true }}/>
let myRoute = {
path: 'picture/:id',
getChildRoutes(location, cb) {
let { state } = location
if (state && state.fromDashboard) {
cb(null, [dashboardPictureRoute])
} else {
cb(null, [pictureRoute])
}
}
}
与 indexRoute
一样,但是是异步的,并且可以接收 location
。与 getChildRoutes
一样,对于 code-splitting 和动态路由匹配很有用
callback
cb(err, route)
// 例如:
let myIndexRoute = {
component: MyIndex
}
let myRoute = {
path: 'courses',
indexRoute: myIndexRoute
}
// 异步的 index route
let myRoute = {
path: 'courses',
getIndexRoute(location, cb) {
// 做一些异步操作
cb(null, myIndexRoute)
}
}
当 route 匹配到 URL 时会渲染一个 route 的组件。路由会在渲染时将以下属性注入组件中:
Router 的 history。
路由 history 对象的方法:
pushState(state, pathname, query)
跳转至一个新的 URL。
参数
state
- location 的 state。pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。replaceState(state, pathname, query)
在不影响 history 长度的情况下(如一个重定向),用新的 URL 替换当前这个。
参数
state
- location 的 state。pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。go(n)
在 history 中使用 n
或 -n
进行前进或后退
goBack()
在 history 中后退。
goForward()
在 history 中前进。
createPath(pathname, query)
使用路由配置,将 query 字符串化加到路径名中。
createHref(pathname, query)
使用路由配置,创建一个 URL。例如,它会在 pathname
的前面加上 #/
给 hash history。
isActive(pathname, query, indexOnly)
根据当前路径是否激活返回 true
或 false
。通过 pathname
匹配到 route 分支下的每个 route 将会是 true(子 route 是激活的情况下,父 route 也是激活的),除非 indexOnly
已经指定了,在这种情况下,它只会匹配到具体的路径。
参数
pathname
- 没有 query 完整的 URL。query
- 如果没有指定,那会是一个包含键值对的对象,并且在当前的 query 中是激活状态的 - 在当前的 query 中明确是 undefined
的值会丢失相应的键indexOnly
- 一个 boolean(默认:false
)。location 对象包含有关当前 URL 的信息。形式大概就像这样:
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: 'something'
} // 仅在 browser history 和 memory history中有效
}
你使用以下几种方式来获取 location 对象:
this.props.location
的方式获取,({ location }) => ()
的方式获取,({ location }) => ()
的方式获取,this.props.location
的方式获取。URL 的动态段。
渲染组件的 route。
this.props.params
是直接在组件中指定 route 的一个子集。例如,如果 route 的路径是 users/:userId
而 URL 是 /users/123/portfolios/345
,那么 this.props.routeParams
会是 {userId: '123'}
,并且 this.props.params
会是 {userId: '123', portfolioId: 345}
。
匹配到子 route 的元素将被渲染。如果 route 有已命名的组件,那么此属性会是 undefined,并且可用的组件会被直接替换到 this.props
上。
render((
<Router>
<Route path="/" component={App}>
<Route path="groups" component={Groups} />
<Route path="users" component={Users} />
</Route>
</Router>
), node)
class App extends React.Component {
render() {
return (
<div>
{/* 这可能是 <Users> 或 <Groups> */}
{this.props.children}
</div>
)
}
}
当一个 route 有一个或多个已命名的组件时,其子元素的可用性是通过 this.props
命名的。因此 this.props.children
将会是 undefined。那么所有的 route 组件都可以参与嵌套。
render((
<Router>
<Route path="/" component={App}>
<Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}} />
<Route path="users" components={{main: Users, sidebar: UsersSidebar}}>
<Route path="users/:userId" component={Profile} />
</Route>
</Route>
</Router>
), node)
class App extends React.Component {
render() {
// 在父 route 中,被匹配的子 route 变成 props
return (
<div>
<div className="Main">
{/* 这可能是 <Groups> 或 <Users> */}
{this.props.main}
</div>
<div className="Sidebar">
{/* 这可能是 <GroupsSidebar> 或 <UsersSidebar> */}
{this.props.sidebar}
</div>
</div>
)
}
}
class Users extends React.Component {
render() {
return (
<div>
{/* 如果在 "/users/123" 路径上这会是 <Profile> */}
{/* UsersSidebar 也会获取到作为 this.props.children 的 <Profile> 。
你可以把它放这渲染 */}
{this.props.children}
</div>
)
}
}
在组件中添加一个钩子,当路由要从 route 组件的配置中跳转出来时被调用,并且有机会去取消这次跳转。主要用于表单的部分填写。
在常规的跳转中, routerWillLeave
会接收到一个单一的参数:我们正要跳转的 location
。去取消此次跳转,返回 false。
提示用户确认,返回一个提示信息(字符串)。在 web 浏览器 beforeunload 事件发生时,routerWillLeave
不会接收到一个 location 的对象(假设你正在使用 useBeforeUnload
history 的增强方法)。在此之上,我们是不可能知道要跳转的 location,因此 routerWillLeave
必须在用户关闭标签之前返回一个提示信息。
beforeunload事件在当页面卸载(关闭)或刷新时调用,事件触发的时候弹出一个有确定和取消的对话框,确定则离开页面,取消则继续待在本页
当路由尝试从一个 route 跳转到另一个并且渲染这个组件时被调用。
nextLocation
: 下一个 location
在组件中添加路由的 history 对象。
注意:你的 route components 不需要这个 mixin,它在 this.props.history
中已经是可用的了。这是为了组件更深次的渲染树中需要访问路由的 history
对象。
跳转至一个新的 URL。
参数
state
- location 的 state。pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。在不影响 history 长度的情况下(如一个重定向),用新的 URL 替换当前这个。
参数
state
- location 的 state。pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。在 history 中使用 n
或 -n
进行前进或后退
在 history 中后退。
在 history 中前进。
使用路由配置,将 query 字符串化加到路径名中。
使用路由配置,创建一个 URL。例如,它会在 pathname
的前面加上 #/
给 hash history。
根据当前路径是否激活,返回 true
或 false
。通过 pathname
匹配到 route 分支下的每个 route 都会是 true(子 route 是激活的情况下,父 route 也是激活的),除非 indexOnly
已经指定了,在这种情况下,它只会匹配到具体的路径。
参数
pathname
- 没有 query 完整的 URL。query
- 如果没有指定,那会是一个包含键值对的对象,并且在当前的 query 中是激活状态的 - 在当前的 query 中明确是 undefined
的值会丢失相应的键indexOnly
- 一个 boolean(默认:false
)。import { History } from 'react-router'
React.createClass({
mixins: [ History ],
render() {
return (
<div>
<div onClick={() => this.history.pushState(null, '/foo')}>Go to foo</div>
<div onClick={() => this.history.replaceState(null, 'bar')}>Go to bar without creating a new history entry</div>
<div onClick={() => this.history.goBack()}>Go back</div>
</div>
)
}
})
假设你正在使用 bootstrap,并且在 Tab 中想让那些 li
获得 active
:
import { Link, History } from 'react-router'
const Tab = React.createClass({
mixins: [ History ],
render() {
let isActive = this.history.isActive(this.props.to, this.props.query)
let className = isActive ? 'active' : ''
return <li className={className}><Link {...this.props}/></li>
}
})
<Tab href="foo">Foo</Tab>
在应用中少数组件由于 History
mixin 的缘故而用得不爽,此时有以下几个选项:
1、让 this.props.history
通过 route 组件到达需要它的组件中。
2、使用 context
import { PropTypes } from 'react-router'
class MyComponent extends React.Component {
doStuff() {
this.context.history.pushState(null, '/some/path')
}
}
MyComponent.contextTypes = { history: PropTypes.history }
3、确保你的 history 是一个 module
4、创建一个高阶的组件,我们可能用它来结束跳转和阻止 history,只是没有时间去思考所有的方法。
// 创建一个高阶的组件
function connectHistory(Component) {
return React.createClass({
mixins: [ History ],
render() {
return <Component {...this.props} history={this.history} />
}
})
}
// 其他文件
// 确保你的 history 是一个 module
import connectHistory from './connectHistory'
class MyComponent extends React.Component {
doStuff() {
this.props.history.pushState(null, '/some/where')
}
}
export default connectHistory(MyComponent)
RouteContext mixin 提供了一个将 route 组件设置到 context 中的便捷方法。这对于 route 渲染元素并且希望用 生命周期 mixin 来阻止跳转是很有必要的。
简单地将 this.context.route
添加到组件中。
返回一个新的 createHistory
函数,它可以用来创建读取 route 的 history 对象。
这个函数被用于服务端渲染。它在渲染之前会匹配一组 route 到一个 location,并且在完成时调用 callback(error, redirectLocation, renderProps)
。
传给回调函数去 match
的三个参数如下:
error
:如果报错时会出现一个 Javascript 的 Error
对象,否则是 undefined
。redirectLocation
:如果 route 重定向时会有一个 Location 对象,否则是 undefined
。renderProps
:当匹配到 route 时 props 应该通过路由的 context,否则是 undefined
。如果这三个参数都是 undefined
,这就意味着在给定的 location 中没有 route 被匹配到。
注意:你可能不想在浏览器中用它,除非你做的是异步 route 的服务端渲染。
创建并返回一个从给定对象 route 的数组,它可能是 JSX 的 route,一个普通对象的 route,或是其他的数组。
routes
一个或多个的 Route
或 PlainRoute
。
react 版本是16.8及以上:
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
16.8以下:
import React from "react";
import { withRouter } from "react-router-dom";
class ScrollToTop extends React.Component {
componentDidUpdate(prevProps) {
if (
this.props.location.pathname !== prevProps.location.pathname
) {
window.scrollTo(0, 0);
}
}
render() {
return null;
}
}
// 确保使用withRouter封装该组件以使其能够访问路由器的props
export default withRouter(ScrollToTop);
然后将其渲染在应用的顶部(整个应用跳转都是滚动到顶部)
function App() {
return (
<Router>
<ScrollToTop>
<App />
</ScrollToTop>
</Router>
);
}
// or just render it bare anywhere you want, but just one :)
<ScrollToTop />;
对于标签按钮跳转(部分页面跳转滚动到顶部)
// 16.8版本及以上
function ScrollToTopOnMount() {
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return null;
}
// Render this somewhere using:
// <Route path="..." children={<LongContent />} />
function LongContent() {
return (
<div>
<ScrollToTopOnMount />
<h1>Here is my long content page</h1>
<p>...</p>
</div>
);
}
// 16.8版本以下
class ScrollToTopOnMount extends React.Component {
componentDidMount() {
window.scrollTo(0, 0);
}
render() {
return null;
}
}
// Render this somewhere using:
// <Route path="..." children={<LongContent />} />
class LongContent extends React.Component {
render() {
return (
<div>
<ScrollToTopOnMount />
<h1>Here is my long content page</h1>
<p>...</p>
</div>
);
}
}
允许您访问用于导航的历史实例
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
返回表示当前URL的location对象。你可以把它想象成一个useState,每当URL改变时,它就会返回一个新的位置。
useParams返回URL参数的键/值对的对象。
上面三个一般都是通过props.history,props.location,props.match.params或props.location.search来获取
useRouteMatch 尝试以与 相同的方式匹配当前URL。它主要用于访问匹配数据,而无需实际渲染。
1、不接受任何参数并返回与当前匹配的match对象
2、接受一个参数,与matchPath的props参数相同。 它可以是字符串的路径名(绝对或相对路径),也可以是带有Route接受的匹配参数的对象。
路由链接(携带参数):
< Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):
< Route path="/demo/test/:name/:age" component=(Test} />
接收参数:
const { name, age } = this.props.match.params
刷新页面参数不消失,参数会在地址栏显示
需要服务器配合
browserHistory模式下, 例如,如果你使用带有 /todos/42
路由的 React 路由器,开发服务器将正确响应 localhost:3000/todos/42
,但是服务于上述生产构建的 Express 不会正确响应。
这是因为当 /todos/42
有新的页面加载时,服务器会查找文件 build/todos/42
并且找不到它。需要配置服务器以通过提供index.html
来响应对 /todos/42
的请求。例如,我们可以修改上面的 Express 示例,为任何未知路径提供 index.html
:
app.use(express.static(path.join(__dirname, 'build')));
-app.get('/', function (req, res) {
+app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
将{ name: 'Tom' , age : 18 } 转换为 'name=tom&age=18'
可以使用qs.stringfy
转换:
import qs from 'querystring'
qs.stringfy({ name: 'Tom' , age : 18 }) // 'name=tom&age=18'
解析:
qs.parse('name=tom&age=18') // { name: 'Tom' , age : 18 }
路由链接(携带参数):
< Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):
< Route path="/demo/test" component={Test} />
接收参数:
const { search } = this.props.location
备注:获取到的 search是 'name=tom&age=18'
这样的字符串,需要借助 querystring 模块的qs.parse解析
刷新页面参数不消失,参数会在地址栏显示
State参数在url中不可见
通过history.push(path, state)
的state仅在 browser history 和 memory history中有效。
hash history 不支持 location.key
或location.state
路由链接(携带参数):
< Link to={{pathname:'/demo/test', search: "?sort=name", state:{name:'tom',age:18}}>详情</Link>
注册路由(无需声明,正常注册即可):
< Route path="/demo/test" component={Test} />
接收参数:
const {state}=this.props.location
1、BrowserRouter(history)模式下,刷新页面参数不消失,参数不会在地址栏显示,因为state保存在history对象中
2、HashRouter 不支持 location.key,location.state
<Route path="*">
<NoMatch />
</Route>
./
改为 /
./
改为 %PUBLIC_URL%
,只适用于create react app, %PUBLIC_URL%
是 public 文件夹的路径import { RouteComponentProps } from 'react-router-dom'
当我们使用Route
组件或者使用withRouter
的时候,都会给组件绑定history,location,match
三个属性:
import React from 'react'
import { withRouter,RouteComponentProps } from 'react-router-dom'
interface Iprops extends RouteComponentProps{}
const App:React.FC<Iprops> = props => {
console.log(props.pathname) // 有提示,并且props有他的类型规定
return (<div></div>)
}
export default withRouter(App)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。