资源
简介
react-router包含3个库,react-router、react-router-dom和react-router-native。react-router提供 最基本的路由功能,实际使用的时候我们不会直接安装react-router,而是根据应用运行的环境选择安 装react-router-dom(在浏览器中使用)或react-router-native(在rn中使用)。react-router-dom和 react-router-native都依赖react-router,所以在安装时,react-router也会自动安装,创建web应用, 使用:
安装
yarn add react-router-dom
基础使用
react-router中奉行一切皆组件的思想,路由器-Router、链接-Link、路由-Route、独占-Switch、重定向Redirect都以组件形式存在
一.创建RouterPage.js
// 一般使用的时候会BrowserRouter as Router
import React, { Component } from "react";
import { BrowserRouter, Link, Route } from "react-router-dom";
import HomePage from "./HomePage";
import UserPage from "./UserPage";
// 根路由要添加exact,实现精确匹配
export default class RouterPage extends Component {
render() {
return (
<div>
<h1>RouterPage</h1>
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
</nav>
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
</BrowserRouter>
</div>
);
}
}
二.配置404页面
设定一个没有path的路由在路由列表最后面,表示一定匹配
// 添加Switch表示仅匹配一个
// 根路由要添加exact,实现精确匹配
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
<Route render={() => <h1>404</h1>} />
</Switch>
三. 动态路由
使用:id的形式定义动态路由
- 定义路由
<Route path="/product/:id" component={Product} />
- 添加导航链接
<Link to={"/product/123"}>搜索</Link>
- 创建Search组件并获取参数
function Product({location, match}) {
console.log("match", match); //sy-log
const {id} = match.params;
return <h1>Product-{id}</h1>;
}
Route渲染内容的三种方式
Route渲染优先级:children>component>render。
三者能接收到同样的(route props),包括match, location, history,但是当不匹配的时候, children的match为null。 这三种方式互斥,只能用一种
1.children----func
- 使用场景: 有时候,不管location是否匹配,你都需要渲染一些内容,这时候可以用children。
- 特性:除了不管location是否匹配都会被渲染之外,其它工作方法与render完全一样。
2. render----func
- 简介:但是当你用render的时候,你调用的只是个函数。但是它和component一样,能访问到所有的(route props)。
3. component---component
- 简介: 只在当location匹配的时候渲染。
- 为什么接收的是组件而不是像children和render接收函数?
因为渲染component的时候会调用React.createElement,如果使用下面这种匿名函数的形式,每次都会生成一个新的匿名的函数。
// 错误示例
// 可以观察child的didMount和willUnmount函数,发现在不停的创建的卸载组件
<Route component={() => <Child count={count} />} />
<Route component={() => <FunctionChild count={count} />} />
- 正确用法
<Route path="/user" component={UserPage} />
4. 注意
当你用 component 的时候,Route会用你指定的组件和React.createElement创建一个新的React element。这意味着当你提供的是一个内联函数的时候,每次render都会创建一个新的组件。这会导 致不再更新已经现有组件,而是直接卸载然后再去挂载一个新的组件。因此,当用到内联函数的内联渲 染时,请使用render或者children。
API
BrowserRouter
- 定义:BrowserRouter 使用 HTML5 提供的 history API ( pushState , replaceState 和 popstate 事件) 来保持 UI 和 URL 的同步。
- 使用:接收一个参数basename: string,表示所有URL的base值。如果你的应用程序部署在服务器的子目录,则需要将其设置为子目录。
<BrowserRouter basename="/zxb">
<Link to="/user" />
</BrowserRouter>
上例中的 最终将被呈现为:
<a href="/zxb/user" />
Link
- to: string。一个字符串形式的链接地址,通过 pathname 、 search 和 hash 属性创建。
<Link to='/courses?sort=name' />
- to: object 一个对象形式的链接地址,可以具有以下任何属性:
- pathname - 要链接到的路径
- search - 查询参数
- hash - URL 中的 hash,例如 #the-hash
- state - 存储到 location 中的额外状态数据
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: {
redirect: '/login'
}
}} />
- others还可以传递一些其它属性,例如 title 、 id 或 className 等
<Link to="/" className="nav" title="a title">About</Link>
Redirect
- to: string,要重定向到的 URL ```
2. to: object
一个对象形式的链接地址,可以具有以下任何属性:
* pathname - 要链接到的路径
* search - 查询参数
* state - 存储到 location 中的额外状态数据
// state 对象可以在重定向到的组件中通过 this.props.location.state 进行访问。 // referrer 键(不是特殊名称)将通过路径名 /login 指向的登录组件中的 this.props.location.state.referrer 进行访问。 <Redirect to={{ pathname: '/login', search: '?utm=your+face', state: { referrer: currentLocation } }} />
### Route
1. 概念: Route可能是 React Router 中最重要的组件。它最基本的职责是在其 path 属性与某个 location 匹配时呈现一些 UI。
2. 渲染方式,children(func)>component(comp)>render(func),注意:三种方式是互的斥的,通常情况下只使用其中的一种。
3.参数--path: string
```
### Router
所有 Router 组件的通用低阶接口。通常情况下,应用程序只会使用其中一个高阶 Router:
* BrowserRouter(pc端最常使用的)
* HashRouter
* MemoryRouter
* NativeRouter(react-native中常用)
* StaticRouter
### Switch
用于渲染与路径匹配的第一个子 Route 或 Redirect 。
Switch只会渲染一个路由。相反,仅仅定义一系列 Route 时,每一个与路径匹配的 Route 都将包含在渲染范围内。考虑如下代码:NoMatch组件会一直被渲染
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={NoMatch} />
Prompt(相当于路由守卫)
源码实现
1. 实现Router---Router.js(接收2个参数(history,children),
主要功能是包一层Context,向下传递history,location,match)
- 定义一个context---context.js
import React from "react";
export const RouterContext = React.createContext();
- 实现router---Router.js ``` import React, {Component} from "react"; import {RouterContext} from "./Context";
export default class Router extends Component {
//
static computeRootMatch(pathname) {
return {path: "/", url: "/", params: {}, isExact: pathname === "/"};
}
constructor(props) {
super(props);
this.state = {
location: props.history.location
};
// 用history原生的listen做监听,location发生变化,要执行这里的回调
this.unlisten = props.history.listen(location => {
this.setState({location});
});
}
// 卸载监听
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
}
}
render() {
// 把history,location,match
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname)
}}>
{this.props.children}
</RouterContext.Provider>
);
}
}
### 2. 实现BrowserRouter ---- BrowserRouter.js
历史记录管理对象history初始化及向下传递,location变更监听
import React, {Component} from "react"; import {createBrowserHistory} from "history"; import Router from "./Router";
export default class BrowserRouter extends Component { constructor(props) { super(props); // 使用history里面的createBrowserHistory方法拿到history,主要用到里面的location的pathname this.history = createBrowserHistory(); } render() { return ; } }
### 3. 实现Route
1.定义一个matchPath方法---matchPath.js
import pathToRegexp from "path-to-regexp";
const cache = {}; const cacheLimit = 10000; let cacheCount = 0;
function compilePath(path, options) {
const cacheKey = ${options.end}${options.strict}${options.sensitive};
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
if (pathCache[path]) return pathCache[path];
const keys = [];
const regexp = pathToRegexp(path, keys, options);
const result = { regexp, keys };
if (cacheCount < cacheLimit) {
pathCache[path] = result;
cacheCount++;
}
return result;
}
/**
- Public API for matching a URL pathname to a path. */ function matchPath(pathname, options = {}) { if (typeof options === "string" || Array.isArray(options)) { options = { path: options }; }
const { path, exact = false, strict = false, sensitive = false } = options;
const paths = [].concat(path);
return paths.reduce((matched, path) => {
if (!path && path !== "") return null;
if (matched) return matched;
const { regexp, keys } = compilePath(path, {
end: exact,
strict,
sensitive
});
const match = regexp.exec(pathname);
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
if (exact && !isExact) return null;
return {
path, // the path used to match
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {})
};
}, null);
}
export default matchPath;
2. 实现Route---Route.js
* 路由配置,匹配检测,内容渲染
* match 按照互斥规则 优先渲染顺序为children component render null,children如果是function执 行function,是节点直接渲染
* 不match children 或者null (只渲染function)
import React, {Component} from "react"; import matchPath from "./matchPath"; import {RouterContext} from "./Context";
export default class Route extends Component { render() { return ( <RouterContext.Consumer> {context => { const location = context.location; const {children, component, render, path, computedMatch} = this.props; const match = computedMatch ? computedMatch : path ? matchPath(location.pathname, this.props) : context.match; console.log("match", match); //sy-log const props = { ...context, match };
// return match ? React.createElement(component, props) : null;
// match children, component, render, null
// 不match children(function), 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>
);
} }
### 4. 实现Link---Link.js
跳转链接,处理点击事件
export default class Link extends Component { static contextType = RouterContext; handleClick = event => { event.preventDefault(); this.context.history.push(this.props.to); }; render() { const {to, children, ...restProps} = this.props; return ( <a href={to} onClick={this.handleClick} {...restProps}> {children} ); } }
### 5.实现Switch方法
import React, {Component} from "react"; import {RouterContext} from "./Context"; import matchPath from "./matchPath";
export default class Switch extends Component { render() { return ( <RouterContext.Consumer> {context => { // match 是否匹配 // element 记录匹配的元素 const {location} = context; let match, element; React.Children.forEach(this.props.children, child => { if (match == null && React.isValidElement(child)) { element = child; const {path} = child.props; match = path ? matchPath(location.pathname, child.props) : context.match; } }); return match ? React.cloneElement(element, { computedMatch: match }) : null; }} </RouterContext.Consumer> ); } }
## hooks实现
// useRouteMatch, // useHistory, // useLocation, // useParams,
import React, {useContext} from "react"; import {RouterContext} from "./Context";
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 : {}; }
## 实现withRouter(主要是传递context)
import React from "react"; import {RouterContext} from "./Context";
const withRouter = WrappedComponent => props => { return ( <RouterContext.Consumer> {context => { return <WrappedComponent {...props} {...context} />; }} </RouterContext.Consumer> ); };
export default withRouter;
## 实现redirect
import React, {Component} from "react"; import {RouterContext} from "./Context";
export default class Redirect extends Component { render() { return ( <RouterContext.Consumer> {context => { const {history} = context; const {to, push = false} = this.props; return ( <LifeCycle onMount={() => { push ? history.push(to) : history.replace(to); }} /> ); }} </RouterContext.Consumer> ); } }
// 只是利用类组件的生命周期进行卸载 class LifeCycle extends Component { componentDidMount() { if (this.props.onMount) { this.props.onMount.call(this, this); } } render() { return null; } }