React Router V4 简介
最近很久没有React Router,好像上一次用还在用V3,最近被问了个关于react routerv4的小问题,怎么也想不起来了,决定自己看着文档,再重新demo一遍复习一下。
package
React Router主要包含三个包: react-router
, react-router-dom
,react-router-native
,react-router
包含了主要的route component和功能。react-router-dom
和react-router-native
提供了基于不同平台(web和mobile)的component(类似于react需要把react和react-dom库分开的原因,为了支持不同的平台)。
Note
但是react-router-dom
和react-router-native
中都包含了react-router
这个库,因此在项目中使用的时候只需要根据你的平台安装上react-router-dom
或者react-router-native
即可
The Router
本文基于浏览器做平台。首先你需要确定使用哪一种Router。
React Router中包含两种router(被分装成component)。
<BrowserRouter>
<HashRouter>
区别
-
HashRouter:
使用URL中的
hash部分
保持URL和页面内容的同步。- 不支持location.key和location.state
-
BrowserRouter:
URL始终和页面的UI同步
- 针对于服务器可以server所有URL,并返回不同的页面
每一个Router组件只能接受一个child component,通常会创建一个App Component作为子组件render app剩下的部分。将应用从 router 中分离对服务器端渲染有重要意义,因为我们在服务器端转换到 <MemoryRouter>
时可以很快复用 <App>
??????
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'))
History
每一个Router(通常一个SPA会有一个Router)都会创建一个History对象,可以将这个对象理解成一个Location object数组,用来存储浏览器URL的变化轨迹。虽然hostory记录着历史,但是你只能从中get到当前的Location object。
每当地址栏发生改变,history对象就会改变(history存储在react的context),因此Router component的update就开始了。保证了此时页面内容会随着每次URL的改变rerender。
因此Router组件必须是最外层组件。
Location Object
Location object反应的是你当前的地址信息,包含了很多从地址栏中分出的信息
{
pathname: '/here',
search: '?key=value',
hash: '#extra-information',
state: { modal: true }, //这个是可以attach到这个location上的数据,但是不会出现在URL中
key: 'abc123' //这个location的唯一标志,也可以理解成数据存放的key
}
Routes
<Route>
组件是 React Router 的主要组成部分,如果你想要在路径符合的时候在任何地方渲染什么东西,你就应该创造一个 <Route>
元素。
What does the <Route> render?
每一个Route component 都必须有一个属性,能够描述当这个route被match的时候应该render些什么。以下三个属性可以用来描述render的内容。
component
当被match的时候,Route 组件会将会使用React.createElement
创建一个component属性
指定类型的React component。
render
属性的类型是一个function,这个function可以返回一个component(类似于react render props)。用于当你想要传递一些属性给被render的component的时候使用
上面两个属性必须使用一个
children
也是一个function return 一个component,对于上两个属性,只有route被匹配的时候才会被render。 这个属性,只要写了,不论任何情况这个component都会被render
Path
Route组件需要一个属性path
,描述了这个route需要匹配的pathname(仅包含port之后search之前的部分)。比如<Route path='/roster'/> 能够匹配上的应该是pathname以/roster
开头的所有URL,一旦match上相应的component就会被render
<Route path='/roster'/>
// when the pathname is '/', the path does not match
// when the pathname is '/roster' or '/roster/2', the path matches
// If you only want to match '/roster', then you need to use
// the "exact" prop. The following will match '/roster', but not
// '/roster/2'.
<Route exact path='/roster'/>
// You might find yourself adding the exact prop to most routes.
// In the future (i.e. v5), the exact prop will likely be true by
// default. For more information on that, you can check out this
// GitHub issue:
// https://github.com/ReactTraining/react-router/issues/4958
Note:
- react-router只会匹配
pathname
,也就意味着对于http://www.example.com/my-projects/one?extra=false
,在react-router中等价于http://www.example.com/my-projects/one
- react-router中route的位置,决定了send的URL匹配的是哪一个route。
- 注意选择是否要加参数
exact
, 因为不使用exact
的时候,只要URL使用path开头就会被匹配
render component props
对于任何一个被匹配的route,这个route被render的component一定能够接受三个属性:
-
location:描述当前的所在的URL
hash: "" pathname: "/" search: "" state: undefined
-
history
react router的Router创建的history对象,其中包含history API中包含的function,还有当前URL的location对象
-
match
isExact: true params: {} path: "/" url: "/"
match object
当URL和某一个route match上的时候,会立刻创建一个match object
url | 被match的URL的pathname |
---|---|
params | path params (/a/b/:id中的id就是path params) |
path | route path属性的值 |
isExact | 是否是exact的完全匹配(path === url) |
Note: match的object存储在匹配的component的props中
使用browserRouter请求/roaster
总是404
此时我的express是这样配置的:
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.resolve(__dirname,'../','./dist')));
module.exports = app;
也就意味着,当请求/roaster
发送过来文件/dist/roaster
一定不存在,所以导致404。
因为单页应用,必须要保证每一个URL发过来都应该返回index.html
然后JS文件回来浏览器再处理route问题
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.resolve(__dirname,'../','./dist')));
app.get('*', function (req,res) {
res.sendFile(path.resolve(__dirname,'../','./dist/index.html'))
});
module.exports = app;
使用browserRouter请求/roaster/id
JS file 404
仔细查看生成的HTML文件,发现<script>
标签包住的JS的打包文件的地址不对类似于roaster/main-12324343543.js
。
原因是HTML webpack plugin生成HTML文件的时候,JS文件的请求地址是这样src=main-12312312.js
(相对的),这种情况会导致请求HTML/roaster/id
, js的请求文件路径直接替换最后slack的部分变成roaster/main-12324343543.js
解决只需要简单的在webpack加上publicPath
output: {
publicPath: '/',
path: __dirname + '/dist',
filename: '[name]-[chunkhash].js'
}
如何创建Nested Routes
由于Routes Component可以用在APP的任意位置,只要作为Router
的child即可。因此你可以使用switch
component将route combine成一个group。
function Main() {
return (
<main>
<Switch>
<Route path='/roster' component={Roster}/>
</Switch>
</main>
);
}
function Roster() {
return (
<div>
<h2>This is a roster page!</h2>
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
</div>
);
}
- 先创建一个Main component将App的所有Route写进去
- 所有用
roster
开头的URL都会match到Main的Roster component
- 所有用
- 然后再Roster component中再细分
/roaster/
之后的path
但是尽管是嵌套路由,在Roaster Component中的Routes仍然需要写full path(是/roster/:numbe
而不是/:number
)
routes component传递data
React Router中使用route传递数据的方式有三种:
-
path variable:数据在URL中:
-
set:
<Route path='/roster/:id' component={Player} /> request: /roster/111
-
get
path variable一般存储在match object中,通过
match.params
即可获取一个object{id:111}
。match object在任意一个被match的route对应的component中都可以通过
this.props.match
获取
-
-
query parameters:数据在query中
-
set:
``` <Route path='/roster' component={Player} /> request: /roster?a=1&b=2&c=3 ```
get
query parameters一般存储在location object中,通过
location.search
即可获取一个string"?a=1&b=2&c=3"
注意会带?
。 -
-
location.state: 数据在location object中,只有在react-router中可以使用。
-
set:由于Link的to属性以及history.push都能能够接受两种类型的值:
string
和 location object。可以通过使用location object作为参数const location = { pathname: "/roster", search: "?a=1", state: {b:2} } <Link to={location} >roster</Link>
一旦点击link就会render Roster component,在这个component中,通过
location.state
就可以获取{b:2}
-
写在最后
- 你可以创建一个没有path属性的Route component,这个Route会和所有URL match
关于history object
-
history objects typically have the following properties and methods:
- length - (number) The number of entries in the history stack
- action - (string) The current action (PUSH, REPLACE, or POP)
- location - (object) The current location. May have the following properties:
- pathname - (string) The path of the URL
- search - (string) The URL query string
- hash - (string) The URL hash fragment
- state - (object) location-specific state that was provided to e.g. push(path, state) when this location was pushed onto the stack. Only available in browser and memory history.
- push(path, [state]) - (function) Pushes a new entry onto the history stack
- replace(path, [state]) - (function) Replaces the current entry on the history stack
- go(n) - (function) Moves the pointer in the history stack by n entries
- goBack() - (function) Equivalent to go(-1)
- goForward() - (function) Equivalent to go(1)
- block(prompt) - (function) Prevents navigation (see the history docs)
-
history对象是可变的,也就是说整个App的history对象只有一个,直接操作它会比较危险。如果你只是想获取location的信息,可以在component中使用
this.props.location
获取,而不是this.props.history.location
class Comp extends React.Component { componentWillReceiveProps(nextProps) { // location都会创建新的 const locationChanged = nextProps.location !== this.props.location // always true // 而history中的location永远只维护一个,每次都是在这上面修改 const locationChanged = nextProps.history.location !== this.props.history.location } // always false
}
```
**Note: location is also found on history.location but you shouldn’t use that because its mutable.**
https://blog.pshrmn.com/entry/a-little-bit-of-history/#anno-5