目录
react-router介绍
Declarative routing for React
react-router仓库的简介是: 为React声明路由
Components are the heart of React's powerful, declarative programming model. React Router is a collection of navigational components that compose declaratively with your application. Whether you want to have bookmarkable URLs for your web app or a composable way to navigate in React Native, React Router works wherever React is rendering--so take your pick!
官方文档中对react-router的介绍是: 组件是React强大的声明式编程模型的核心. React Router是一组以声明方式与你的应用程序组合起来的导航组件集合. 不管你是否想要为你的web应用添加可书签的url, 或是在React Native中添加一个可组合的导航方式, React Router都能在React渲染的地方工作, 所以你可以选择!
简而言之, react-router为React提供了路由能力, 不管是web应用或是React Native应用, 都可以使用react-router进行路由管理;
这里只对web应用的路由原理进行分析
react-router路由跳转原理
常见路由模式
SPA(单页面应用)的路由模式一般分为两种:
hash模式history模式
源码分析react-router路由跳转
在引入了react-router的React应用中, 我们通常使用react-router-dom提供的Link组件进行路由跳转; 在Link组件中, 路由跳转相关代码如下:
const method = replace ? history.replace : history.push;
method(location);
replace表示是否替换当前路由, location表示跳转的路由
可以看出, react-router实现路由跳转主要使用了history.replace以及history.push, 往上层探究后发现, 这里的history是react-router开发者实现的一个库, 对window.history进行封装, 利用window.history.pushState和window.history.replaceState两个api, 实现url跳转而无须重新加载页面;
模拟react-router路由跳转
react-router中路由跳转之类的路由操作便是通过history库完成的, 下面使用create-react-app(react^16.13.1)写了一个小栗子🌰, 简单实现了一下history路由跳转的原理:
History.ts
interface Listener {
(url: string): void
};
interface History {
listeners: Array<Listener>,
push: {
(url: string, state?: {[propsName: string]: any} | null): void
},
listen: {
(fn: Listener): {(): void}
}
};
const createHistory = (): History => {
const globalHistory = window.history;
const _history: History = {
listeners: [],
listen(fn) {
this.listeners.push(fn);
return () => {
let i: number = -1;
this.listeners.find((listener, index) => {
if (listener === fn) {
i = index;
}
return listener === fn;
});
if (i !== -1) {
this.listeners.splice(i, 1);
}
};
},
push(url, state) {
globalHistory.pushState(state, '', url);
this.listeners.forEach(listener => {
listener(url);
});
}
};
return _history;
};
export default createHistory;
上面是一个简单实现的history库, 只实现了push的功能(未实现replace功能), 主要分为三个部分:
listeners: 数组类型, 当history.push调用时, 依次执行listeners中的函数;listen: 函数类型, 接受一个函数listener作参数, 并将listener加到listeners中, 等待history.push执行; 返回一个函数unlisten, 执行时将当前的listener从listeners中移除;push: 函数类型, 接收一个url作为参数, 执行globalHistory.pushState(此处的globalHistory为window.history), 并依次执行listeners中所有函数;
从上面代码可以看出, history主要运用了订阅-发布设计模式的思想;
App.ts
import React, {useEffect, useState} from 'react';
import createHistory from './history';
const history = createHistory();
const Page1: React.FC = props => {
return <div>Page1</div>;
};
const Page2: React.FC = props => {
return <div>Page2</div>;
};
const App: React.FC = props => {
const [location, setLocation] = useState<string>(window.location.pathname);
const pushHistory = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>, url: string): void => {
event.preventDefault();
history.push(url);
};
const renderComponent = (): ReactElement => {
switch (location) {
case '/page1': {
return <Page1></Page1>;
}
case '/page2': {
return <Page2></Page2>;
}
default: {
return <Page1></Page1>;
}
}
};
useEffect(() => {
// 页面首次渲染完成后执行
history.listen((url) => {
setLocation(url);
});
}, []);
return (
<div>
<div className="nav">
<a href="/page1" onClick={(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => pushHistory(event, '/page1')}>page1</a>
<a href="/page2" onClick={(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => pushHistory(event, '/page2')}>page2</a>
</div>
<div>{renderComponent()}</div>
</div>
);
};
export default App;
上面的代码生成的页面结构分为:
- 导航部分: 对超链接的默认事件进行阻止, 避免刷新页面, 并绑定新的点击事件, 触发
history.push进行路由跳转; - 路由组件渲染部分: 通过
location变量渲染对应的路由组件;
代码逻辑结构如下:
- 创建一个
history示例; - 执行
renderComponent函数, 渲染出当前路由对应组件; App首次渲染完成时使用history.listen注册一个监听事件, 事件调用时使用setLocation将location设置为url参数; 并将history.listen返回的函数赋值给变量unlisten;- 点击超链接, 执行
history.push跳转路由, 执行history.listen中的回调函数, 执行setLocation修改location变量的值, 导致组件重新渲染,renderComponent函数重新执行, 路由组件成功渲染; - 退出页面时, 执行
unlisten函数, 销毁当前监听事件;
总结
这篇文章主要是对react-router中路由跳转原理的分析, 并自行实现了一个简单的history库, 当然history库的逻辑更为复杂, 这里并不深究; 如果喜欢请点个赞吧, 下一篇文章将会接着对react-router的组件进行源码分析, 冲!
如果发现文章有错误可以在评论区里留言哦, 欢迎指正!