本文主要是通过创建一个基本版本的React Router v4 来理解背后实现的原理。如果要学习React Router怎么使用,请移步官方文档
Route
Route
是用来渲染 UI的,具体点就是当一个 URL 匹配上了你所指定的路由路径,就进行渲染。
Route 组件常用的属性主要有四个exact
、path
、component
和render
,这个四个属性的主要作用为:
exact: 只有当所给路径精确匹配上 location.pathname 时才返回 true。
path: 非必须属性,如果改属性不存在,那么路由对应的组件将自动渲染
component: 如果路径匹配上了,则渲染属性对应的组件
render: 允许你创建一个直接返回 UI 的内联函数而不用创建额外的组件
Route
的功能是渲染匹配指定路由路径的组件,所以Router
需要做到的功能为:判断当前的URL
是否和组件的path
相匹配,如果匹配则返回渲染的UI
;否则返回null
。
1 | // 判断path是否匹配 |
上面的代码即实现了:如果匹配上了 path 属性,就返回 UI,否则什么也不做。
这里还有一种情况就是点击浏览器前进/后退按钮改变URL
的时候,需要让Route
可以做出针对性的处理。
当用户点击了后退/前进按钮的时候,popstate
事件会被触发,因此只需要监听popstate
事件,在URL
被改变时,触发popstate
去检查是否匹配上了新的 URL,如果是则渲染 UI,如果不是,什么也不做。
1 | // 判断path是否匹配 |
这样就实现了根据后退/前进按钮来“重匹配”、“重判断”和“重渲染”。
Link
Link
主要是解决通过a
标签改变URL
的时候,重新匹配Route
组件并渲染的问题。Link
一般是这样的使用的<Link to='/some-path' replace={false} />
,to
是一个string
类型,表示要跳转到的链接。replace
是布尔类型,如果为true
,则将替换history
中的最后一个链接替换为当前的链接,否则就添加当前链接到history
中。
首先,Link
需要渲染一个a
标签,并且需要组织a
的默认动作,以免全页面刷新,所以需要一个click
事件处理函数来阻止a
的默认动作。
1 | class Link extends React.Component{ |
然后需要处理更新URL
的部分,通过history
的api
更新路由,我们需要在点击事件中,获取目标URL
,然后通知目标URL
对应的Route
组件渲染。
为了可以准确的渲染路由对应的组件,我们需要将所有的路由收集起来,没当地址发生改变的时候,就遍历数组,并调用forceUpdate
函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 这里创建的两个函数,用来收集Route和删除Route
let instances = [];
const register = (comp)=>{instances.push(comp);};
const unregister = (comp)=>{instances.splice(instances.indexOf(comp),1);}
// 更新 Route 组件
// 判断path是否匹配
const matchPatch = (pathname, options) => {
...
}
class Route extend React.Component{
...
componentDidMount(){
// 加了一个 popstate 监听,当 popstate 触发的时候,调用 forceUpdate 来强制做重新渲染的判断。
window.addEventListener("popstate",this.handlePopstate);
register(this);
}
componentWillUnMount(){
// 组件卸载时,移除监听事件
window.removeEventListener("popstate",this.handlePopstate);
unregister(this)
}
handlePopstate(){
this.forceUpdate();
}
...
}
// 更新 Link 组件
class Link extends React.Component{
static propTypes = {
to: PropTypes.string.isRequired,
replace: PropTypes.bool
}
onClick(e){
e.preventDefault();
let {to,replace} = this.props;
if(replace){
history.replaceState({},null,to)
}else{
history.pushState({},null,to)
}
instances.forEach(item=>item.forceUpdate());
}
render(){
return (
<a href={this.props.to} onClick={this.handleClick}>{this.props.children}</a>
)
}
}
Redirect
1 | class Redirect extends React.Component{ |