2021-02-04 React-router的使用及源码API实现
react-router简介
react-router包含三个库,react-router、react-router-dom和react-router-native。react-router提供最基本的路由功能,实际使用的时候根据应用运行的环境选择安装react-router-dom(在浏览器中使用)或react-router-native(在react-native中使用)
yarn add react-router-dom 或
yarn add react-router-native
基本使用
react-router中奉行一切皆组件的思想,路由器-Router、链接-Link、路由Route、独占Switch、重定向-Redirect都已组件的形式存在
import React from "react";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import HomePage from "./HomePage";
import LoginPage from "./LoginPage";
import UserPage from "./UserPage";
function RouterPage() {
return (
<div>
<h3>RouterPage</h3>
<Router>
<nav>
<Link to="/">首页</Link> <Link to="/user">用户中心</Link>{" "}
<Link to="/login">登录</Link> <Link to="/product/123">商品</Link>
</nav>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/user" component={UserPage} />
<Route path="/login" component={LoginPage} />
<Route render={() => <h3>404 not found</h3>} />
</Switch>
</Router>
</div>
);
}
export default RouterPage;
Route渲染内容的三种方式
Route渲染优先级:children>component>render
三者都能收到同样的[route props],包括match,location,history,但是当不匹配的时候的children的match为null
这三种方式互斥,只能选择其中一种
children:func
有时候,不管location是否匹配,你都要渲染一些内容,这时候可以用children,除了不管location是否都匹配都会被渲染之外,其他工作方式与render完全一样,另外用了Switch独占组件还是选择条件选择匹配
render:func
当你用render的时候,你调用的是只是个函数,但是它和component一样,能访问到所有的[route props]
component:component
只有当location匹配的时候才渲染
注意:当用
component
的时候,Route会用你指定的组件和React.createElement创建一个新的[React element]。这意味着你提供的是一个内联函数的时候,每次render都会创建一个新的组件。这会导致不再更新已有组件,而是直接卸载然后挂载一个新的组件。因此,当用到内联函数的内联渲染时,请使用render或者children
摘自源码片段:
BrowserRouter与HashRouter对比
1、HashRouter最简单,不需要服务器渲染,靠浏览器的#来区分path就可以,BrowserRouter需要服务器端对不同的URL返回不同的HTML,后端需要配置
2、BrowserRouter使用HTML5 history API( pushState,replaceState和popstate事件),让页面的UI同步与URL
3、HashRouter不支持location.key和location.state,动态路由跳转需要通过?
传递参数
4、Hash history不需要服务器任何配置就可以运行
API源码实现
Router
所有Router组件的通用低阶接口。通常情况下,会使用以下其中一个高阶Router:
- BrowserRouter
- HashRouter
- MemoryRouter
- NativeRouter
- StaticRouter
import React, { Component } from "react";
import RouterContext from "./RouterContext";
class Router extends Component {
static computeRootMatch(pathname) {
return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
}
constructor(props) {
super(props);
this.state = {
location: props.history.location
};
// 监听location变化
this.unlisten = props.history.listen(location => {
this.setState({ location });
});
}
componentWillUnmount() {
// 取消监听
if (this.unlisten) {
this.unlisten();
}
}
render() {
const { history, children } = this.props;
return (
<RouterContext.Provider
value={{
history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname)
}}
>
{children}
</RouterContext.Provider>
);
}
}
export default Router;
BrowserRouter
<BrowserRouter>
使用HTML5提供的history API(pushState
,replaceState
和popState
事件)来保持UI和URL的同步
HashRouter
<HashRouter>
使用URL的hash
部分(即window.location.hash
)来保持UI和URL的同步
MemoryRouter
把URL的历史记录保存在内存中的<Router>
(不读取、不写入地址栏)。在测试和非浏览器环境中很有用,如React Native
实现BrowserRouter
BrowserRouter:历史记录管理对象history初始化以及向下传递,location变更监听
import { createBrowserHistory } from "history";
import { Component } from "react";
import Router from "./Router";
class BrowserRouter extends Component {
constructor(props) {
super(props);
this.history = createBrowserHistory();
}
render() {
return <Router children={this.props.children} history={this.history} />;
}
}
export default BrowserRouter;
Route
<Route>
是React Router中最重要的组件,其最基本的职责是在其path属性与某个location匹配时呈现一些UI
使用<Route>
渲染方式有以下三种:
- component
- render: func
- children: func
在不同情况下使用不同的方式,在指定的<Route>
中应该只使用其中一种
路由配置,匹配检测,内容渲染
match按照互斥规则,优先渲染顺序为children>component>render>null,child如果是function执行function,是节点直接渲染
不match children或者null(只渲染function)
import React, { Component } from "react";
import matchPath from "./matchPath";
import RouterContext from "./RouterContext";
class Route extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const { location } = context;
const {
children,
component,
render,
path,
computedMatch
} = this.props;
const match = computedMatch
? computedMatch
: path
? matchPath(location.pathname, this.props)
: context.match; // location.pathname === path
const props = { ...context, match };
// match
// 匹配 children, component, render, null
// 不匹配 children(function)
// return match?React.createElement(component):null
return (
<RouterContext.Provider value={props}>
{match
? children
? typeof children === "function"
? children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? children(props)
: null}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}
export default Route;
Link
<Link>
跳转链接,处理点击事件
参数
to: string | object
一个字符串或object对象形式的链接地址,通过pathname,search,hash,state等创建
replace: bool
当设置为true时,点击链接后将替换历史堆栈中的当前条目,而不是添加新条目。默认为false
others属性如title,id,className等
import { useContext } from "react";
import RouterContext from "./RouterContext";
function Link({ children, to, ...restProps }) {
const { history } = useContext(RouterContext);
const handleClick = e => {
e.preventDefault(); // 阻止默认点击行为
// 命令式跳转
history.push(to);
};
return (
<a href={to} {...restProps} onClick={handleClick}>
{children}
</a>
);
}
export default Link;
Switch
用于渲染与路径匹配的第一个子<Route>
或<Redirect>
<Switch>
只会渲染一个路由
import React, { Component } from "react";
import matchPath from "./matchPath";
import RouterContext from "./RouterContext";
class Switch extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const { location } = context;
let match; // 标记匹配
let element; // 标记匹配的元素
// todo 遍历子元素
// children
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
match = child.props.path
? matchPath(location.pathname, child.props)
: context.match;
}
});
return match
? React.cloneElement(element, { computedMatch: match })
: null;
}}
</RouterContext.Consumer>
);
}
}
export default Switch;
hooks实现
import { useContext } from "react";
import RouterContext from "./RouterContext";
// 自定义hooks
export function useHistory() {
return useContext(RouterContext).history;
}
export function useLocation() {
return useContext(RouterContext).location;
}
export function useRouteMatch() {
return useContext(RouterContext).match;
}
export function useParams() {
const match = useContext(RouterContext).match;
return match ? match.params : {};
}