react-router学习笔记
Posted 好_快
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react-router学习笔记相关的知识,希望对你有一定的参考价值。
前端路由
- 路由这个概念最早出现在后端,通过⽤户请求的url导航到具体的html⻚⾯。
- 现在的前端路由不同 于传统路由,它不需要服务器解析,⽽是可以通过hash函数或者history API来实现。
- 在前端开发中,可以使⽤路由设置访问路径,并根据路径与组件的映射关系切换组件的显示
- 这整个过程都是在同 ⼀个⻚⾯中实现的,不涉及⻚⾯间的跳转,这也就是常说的单⻚应⽤(spa)。
1、查看源码姿势
1.1 代码仓库
https://github.com/ReactTraining/react-router
2.2 包说明
- react-router 公共基础包
- react-router-dom 在浏览器中使⽤,依赖react-router
- react-router-native 在react-native中使用,依赖react-router
2.3 源码位置
- react-router 源码位置
react-router/packages/react-router/modules/
- react-router-dom 源码位置
react-router/packages/react-router-dom/modules/
2.4 文件结构
Hooks
暂不关注<Link>
<NavLink>
<Prompt>
暂不关注<Redirect>
<Route>
<Router>
<HashRouter>
<BrowserRouter>
<MemoryRouter>
暂不关注<StaticRouter>
暂不关注
<Switch>
- generatePath
暂不关注 - history
- location
暂不关注 - match
暂不关注 - matchPath
暂不关注 - withRouter
3、React-Router源码分析
3.1 四种 Router 源码对比
<!--HashRouter-->
//便于梳理代码结构,已删除部分代码
import React from "react";
import Router from "react-router";
import createHashHistory as createHistory from "history";
/**
* The public API for a <Router> that uses window.location.hash.
*/
class HashRouter extends React.Component
history = createHistory(this.props);
render()
return <Router history=this.history children=this.props.children />;
export default HashRouter;
<!--BrowserRouter-->
//便于梳理代码结构,已删除部分代码
import React from "react";
import Router from "react-router";
import createBrowserHistory as createHistory from "history";
/**
* The public API for a <Router> that uses HTML5 history.
*/
class BrowserRouter extends React.Component
history = createHistory(this.props);
render()
return <Router history=this.history children=this.props.children />;
export default BrowserRouter;
<!--MemoryRouter-->
//便于梳理代码结构,已删除部分代码
import React from "react";
import createMemoryHistory as createHistory from "history";
import Router from "./Router.js";
/**
* The public API for a <Router> that stores location in memory.
*/
class MemoryRouter extends React.Component
history = createHistory(this.props);
render()
return <Router history=this.history children=this.props.children />;
export default MemoryRouter;
<!--StaticRouter-->
//便于梳理代码结构,已删除部分代码
import React from "react";
import createLocation, createPath from "history";
import Router from "./Router.js";
/**
* The public top-level API for a "static" <Router>, so-called because it
* can't actually change the current location. Instead, it just records
* location changes in a context object. Useful mainly in testing and
* server-rendering scenarios.
*/
class StaticRouter extends React.Component
render()
const basename = "", context = , location = "/", ...rest = this.props;
const history =
createHref: path => addLeadingSlash(basename + createURL(path)),
action: "POP",
location: stripBasename(basename, createLocation(location)),
push: this.handlePush,
replace: this.handleReplace,
go: staticHandler("go"),
goBack: staticHandler("goBack"),
goForward: staticHandler("goForward"),
listen: this.handleListen,
block: this.handleBlock
;
return <Router ...rest history=history staticContext=context />;
export default StaticRouter;
都是基于组件实现,区别就是传递不同的参数。重点关注 和
3.2 源码分析
<!--Router-->
//便于梳理代码结构,已删除部分代码
class Router extends React.Component
render()
return (
<RouterContext.Provider
value=
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
//只有StaticRouter在使用,暂不考虑
staticContext: this.props.staticContext
>
/*HistoryContext在hooks中使用,暂不考虑*/
<HistoryContext.Provider
children=this.props.children || null
value=this.props.history
/>
</RouterContext.Provider>
);
通过源码可以看出Router的核心功能就是提供以下数据
- history,父组件传入,由history库生成
- location,组件内计算生成
- match,组件内静态方法计算生成
3.3 源码分析
<!--Route-->
//便于梳理代码结构,已删除部分代码
class Route extends React.Component
render()
return (
<RouterContext.Consumer>
context =>
return (
<RouterContext.Provider value=props>
props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null
</RouterContext.Provider>
);
</RouterContext.Consumer>
);
由于三项表达式嵌套不便于阅读,代码可以转换成
/*
* 1、检测是否 match
* 1.1 不匹配,children 为函数则返回 children(props),否则返回 null
* 1.2 匹配,进行第2步
*
* 2、检查 children
* 2.1 存在, children 为函数则返回 children(props),否则返回 children
* 2.2 不存在,进行第3步
*
* 3、检查component
* 3.1 存在,返回React.createElement(component, props)
* 3.2 不在存,进行第4步
*
* 4、检查render
* 4.1 存在,返回 render(props)
* 4.2 不存在,返回 null
* */
getComponent(props, children, component, render)
if (props.match)
if (children)
if (typeof children === "function")
return children(props);
else
return children;
else
if (component)
return React.createElement(component, props);
else
if (render)
return render(props);
else
return null;
else
if (typeof children === "function")
return children(props);
else
return null;
通过源码可以看出Reoute的核心功能是渲染组件,并有以下特点
- 三者优先级 children > component > render
- children 为函数可以做到匹配与否都显示
- children 为组件只能在匹配显示
- component 只能为组件
- render 只能为函数
3.4 源码分析
<!--Switch-->
//便于梳理代码结构,已删除部分代码
function Redirect( computedMatch, to, push = false )
const method = push ? history.push : history.replace;
return (
<RouterContext.Consumer>
context =>
return (
<Lifecycle
onMount=() =>
method(location);
onUpdate=(self, prevProps) =>
method(location);
to=to
/>
);
</RouterContext.Consumer>
);
通过源码可以看出 Redirect 的核心功能是跳转到 to 指向的页面。
3.5
源码分析
<!--Switch-->
//便于梳理代码结构,已删除部分代码
class Switch extends React.Component
render()
return (
<RouterContext.Consumer>
context =>
const location = this.props.location || context.location;
let element, match;
// 匹配第一个Route
React.Children.forEach(this.props.children, child =>
if (match == null && React.isValidElement(child))
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, ...child.props, path )
: context.match;
);
//匹配则返回React生成的元素,否则返回null
return match
? React.cloneElement(element, location, computedMatch: match )
: null;
</RouterContext.Consumer>
);
通过源码可以看出 Switch 的核心功能是渲染匹配到第一个 Route 组件。
3.6 withRouter 源码分析
<!--withRouter-->
//便于梳理代码结构,已删除部分代码
function withRouter(Component)
const C = props =>
const wrappedComponentRef, ...remainingProps = props;
return (
//通过 Context.Consumer 传递 Router 属性给 Component,达到增强目的
<RouterContext.Consumer>
context =>
return (
<Component
...remainingProps
...context
ref=wrappedComponentRef
/>
);
</RouterContext.Consumer>
);
;
//静态属性拷贝
return hoistStatics(C, Component);
通过源码可以看出 withRouter 的核心功能是 传递 Router 属性给 Component。
3.7 源码分析
<!--Link-->
//便于梳理代码结构,已删除部分代码
const Link = forwardRef(
(
component = LinkAnchor,
replace,
to,
innerRef, // TODO: deprecate
...rest
,
forwardedRef
) =>
return (
<RouterContext.Consumer>
context =>
const history = context;
//把 to 属性转换成 href 属性
const location = normalizeToLocation(
resolveToLocation(to, context.location),
context.location
);
const href = location ? history.createHref(location) : "";
//组装 props 数据
const props =
...rest,
href,
navigate()
const location = resolveToLocation(to, context.location);
const method = replace ? history.replace : history.push;
method(location);
;
// 设置forwardedRef
props.ref = forwardedRef
//创建并返回组件
return React.createElement(component, props);
</RouterContext.Consumer>
);
);
<!--LinkAnchor-->
//便于梳理代码结构,已删除部分代码
const LinkAnchor = forwardRef(
(
innerRef, // TODO: deprecate
navigate,
onClick,
...rest
,
forwardedRef
) =>
const target = rest;
//组装 props 数据
let props =
...rest,
onClick: event =>
//处理点击事件
try
if (onClick) onClick(event);
catch (ex)
event.preventDefault();
throw ex;
//执行路由跳转事件
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!target || target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
)
event.preventDefault();
navigate();
;
// 设置forwardedRef
props.ref = forwardedRef;
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
return <a ...props />;
);
通过源码可以看出 Link 的核心功能如下
- 通过 component 自定义 ,否则默认使用
- 的主要功能就是把 to 属性转换成 href 属性
- 主要功能是屏蔽 默认点击事件,使用 history 进行路由跳转
3.8 源码分析
<!--NavLink-->
//便于梳理代码结构,已删除部分代码
const NavLink = forwardRef(
(
"aria-current": ariaCurrent = "page",
activeClassName = "active",
activeStyle,
className: classNameProp,
exact,
isActive: isActiveProp,
location: locationProp,
sensitive,
strict,
style: styleProp,
to,
innerRef, // TODO: deprecate
...rest
,
forwardedRef
) =>
return (
<RouterContext.Consumer>
context =>
//根据 isActive 属性判断是否 Active
const isActive = !!(isActiveProp
? isActiveProp(match, currentLocation)
: match);
//根据 isActive 属性设置 className
const className = isActive
? joinClassnames(classNameProp, activeClassName)
: classNameProp;
//根据 isActive 属性设置内联样式 style
const style = isActive ? ...styleProp, ...activeStyle : styleProp;
//组织 props 数据
const props =
"aria-current": (isActive && ariaCurrent) || null,
className,
style,
to: toLocation,
...rest
;
// 设置forwardedRef
props.ref = forwardedRef;
return <Link ...props />;
</RouterContext.Consumer>
);
);
通过源码可以看出 的核心功能如下
- 在 基础上允许自定义默认和激活状态样式
- isActive 优先级高于默认的路由匹配
4、history 源码分析
4.1 createBrowserHistory
<!--createBrowserHistory-->
//便于梳理代码结构,已删除部分代码
export function createBrowserHistory( options: BrowserHistoryOptions = ): BrowserHistory
//获取 history
let window = document.defaultView! = options;
let globalHistory = window.history;
window.addEventListener(PopStateEventType, handlePop);
let action = Action.Pop;
let [index, location] 以上是关于react-router学习笔记的主要内容,如果未能解决你的问题,请参考以下文章