本篇文章将对react-router中残余的组件进行源码剖析
<Redirect>
和其余的路由组件一样, <Redirect>
应用<RouterContext.Consumer>
接管路由数据;
定义<Redirect>
的prop-types
Redirect.propTypes = { push: PropTypes.bool, from: PropTypes.string, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired};
<Redirect>
的渲染逻辑
首先通过传入的push
确定<Redirect>
的跳转形式是push还是replace:
// push为props.push, 默认为falseconst method = push ? history.push : history.replace;
接着确定跳转的location
: createLocation
为history
库的办法, 会依据传入的参数创立一个location
对象:
// to为props.to, computedMatch为props.computedMatchconst location = createLocation( computedMatch ? typeof to === "string" ? generatePath(to, computedMatch.params) : { ...to, pathname: generatePath(to.pathname, computedMatch.params) } : to);
注:
- 当
<Redirect>
作为<Switch>
的子组件并被匹配时,<Switch>
将会将匹配计算得出的computedMatch
传给<Redirect>
generatePath
是react-router提供的一个api, 用于将path和parameters组合生成一个pathname
接下来就是<Redirect>
跳转逻辑实现:
<Lifecycle onMount={() => { method(location); }} onUpdate={(self, prevProps) => { const prevLocation = createLocation(prevProps.to); if ( !locationsAreEqual(prevLocation, { ...location, key: prevLocation.key }) ) { method(location); } }} to={to}/>
<Lifecycle>
组件构造非常简单, 反对传入onMount
, onUpdate
以及onUnmount
三个办法, 别离代表着componentDidMount
, componentDidUpdate
, componentWillUnmount
;
因而<Redirect>
应用Lifecycle
触发的动作如下:
<Redirect>
在componentDidMount
生命周期中进行push/replace跳转;- 在
componentDidUpdate
生命周期中应用history
库的locationsAreEqual
办法, 比拟上一个location和新的location是否雷同, 若是location不雷同, 则执行push/replace跳转事件;
// LifeCycle.jsimport React from "react";class Lifecycle extends React.Component { componentDidMount() { if (this.props.onMount) this.props.onMount.call(this, this); } componentDidUpdate(prevProps) { if (this.props.onUpdate) this.props.onUpdate.call(this, this, prevProps); } componentWillUnmount() { if (this.props.onUnmount) this.props.onUnmount.call(this, this); } render() { return null; }}export default Lifecycle;
<Link>
<Link>
实现了react-router中路由跳转;
定义<Link>
的prop-types
const toType = PropTypes.oneOfType([ PropTypes.string, PropTypes.object, PropTypes.func]);const refType = PropTypes.oneOfType([ PropTypes.string, PropTypes.func, PropTypes.shape({ current: PropTypes.any })]);Link.displayName = "Link";Link.propTypes = { innerRef: refType, onClick: PropTypes.func, replace: PropTypes.bool, target: PropTypes.string, to: toType.isRequired};
实际上<Link>
还有一个prop: component
, 但不分明这里为什么不对component
进行类型申明;
<Link>
的渲染逻辑
<Link>
应用<RouterContext.Consumer>
接管路由信息;
<Link>
通过对props.to
进行解决, 得出href
属性, 申明props
对象:
( { component = LinkAnchor, replace, to, innerRef, // TODO: deprecate ...rest }) => { // ... 通过解决props.to得出href const props = { ...rest, href, navigate() { const location = resolveToLocation(to, context.location); const method = replace ? history.replace : history.push; method(location); } }; // ...}
并将下面得出的props
注入component
中:
return React.createElement(component, props);
从源码能够看到, 此处的component
默认为LinkAnchor
, 因而咱们来浏览以下<LinkAnchor>
的源码:
LinkAnchor
的props构造如下:
{ innerRef, // TODO: deprecate navigate, onClick, ...rest}
次要是navigate
以及onClick
:
navigate
从<Link>
源码中能够看到, 次要是通过传入的replace
属性判断跳转类型, 依据对应跳转类型抉择history.replace
或是history.push
进行路由跳转:
navigate() { const location = resolveToLocation(to, context.location); const method = replace ? history.replace : history.push; method(location);}
onClick
更好了解, 是<Link>
组件的点击事件申明;
<LinkAnchor>
通过传入的props生成了一个props
, 并返回一个注入了props
的超链接:
let props = { // ...};return <a {...props} />;
次要性能实现在于超链接的onClick
, 点击事件中首先判断是否存在props.onClick
, 存在的话则立刻执行; 接着进行是否执行props.navigate
的判断:
是否进行跳转须要满足以下所有条件:
event.button === 0
: 点击事件为鼠标左键;!target || target === "_self"
:_target
不存在, 或者_target
为_self
;!isModifiedEvent(event)
: 点击事件产生时未有其余按键同时按住;注:
isModifiedEvent
用于判断点击事件产生时是否有其余按键同时按住;
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();
event.preventDefault()
阻止超链接默认事件, 防止点击<Link>
后从新刷新页面;
navigate()
应用history.push
或history.replace
进行路由跳转, 并触发<Router>
中申明的history
监听事件, 从新渲染路由组件!
withRouter
定义withRouter
的prop-types
wrappedComponentRef
使得高阶组件可能拜访到它包裹组件的ref
;
C.propTypes = { wrappedComponentRef: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, PropTypes.object ])};
withRouter
的渲染逻辑
withRouter
是一个高阶组件, 反对传入一个组件, 返回一个能拜访路由数据的路由组件, 本质上是将组件作为<RouterContext.Consumer>
的子组件, 并将context
的路由信息作为props
注入组件中;
const C = props => { // ...返回组件 const { wrappedComponentRef, ...remainingProps } = props; return ( <RouterContext.Consumer> {context => { return ( <Component {...remainingProps} {...context} ref={wrappedComponentRef} /> ); }} </RouterContext.Consumer> );};return hoistStatics(C, Component);
hoistStatics
是三方库hoist-non-react-statics
, 用于解决高阶组件中原组件static失落的问题; 同时应用反对传入props: wrappedComponentRef
, wrappedComponentRef
绑定原组件的ref
, 因而能够通过wrappedComponentRef
拜访到原组件; 须要留神的是, 函数式组件没有ref
, 因为函数式组件并没有实例, 所以应用withRouter
包裹函数式组件时, 不反对应用wrappedComponentRef
拜访原组件!
Hooks
React Router ships with a few hooks that let you access the state of the router and perform navigation from inside your components.Please note: You need to be using React >= 16.8 in order to use any of these hooks!
react-router提供了一些hooks, 让咱们能够在组件中获取到路由的状态并且执行导航; 如果须要应用这些钩子, 咱们须要应用React >= 16.8
;
react-router的hooks实际上是利用React提供的hooks: useContext
, 让咱们能够在组件中拜访到HistoryContext
以及RouterContext
中的数据;
useHistory
import React from 'react';import HistoryContext from "./HistoryContext.js";const useContext = React.useContext;export function useHistory() { return useContext(HistoryContext);};
useLocation
import React from 'react';import RouterContext from "./RouterContext.js";const useContext = React.useContext;export function useLocation() { return useContext(RouterContext).location;};
useParams
import React from 'react';import RouterContext from "./RouterContext.js";const useContext = React.useContext;export function useParams() { const match = useContext(RouterContext).match; return match ? match.params : {};};
useRouteMatch
import React from 'react';import RouterContext from "./RouterContext.js";import matchPath from "./matchPath.js";const useContext = React.useContext;export function useRouteMatch(path) { const location = useLocation(); const match = useContext(RouterContext).match; return path ? matchPath(location.pathname, path) : match;}
注:
- useRouteMatch应用hook:
useLocation
, 去获取location; - matchPath是react-router的一个公共api, 反对传入一个
pathname
以及path
, 若是path
与pathname
匹配则返回一个match
对象, 不匹配则返回一个null
;
结尾
从源码对react-router v5进行原理剖析系列到此结束, 实际上还有一些比拟冷的组件没有进行源码浏览(挖个坑, 当前有空能够填);
认真想想, 这还是第一次系统性地去浏览一个高星的库, 这次源码浏览让我感觉受益匪浅, 比照一下本人写的库, 不论是从设计还是总体封装都是差了十万八千里(笑, 还得努致力;
作者之前是偏差vue, 因为最近开始系统性地学React, 所以想趁着学习的激情, 把React一些高星的库挖挖, 看看能不能从源码中了解到一些react开发中的小技巧或是设计思维, 所以目标是达到了;
感叹一下: React的生态是真的凋敝, 根底库也是多到目迷五色, 其实在我看来这也算个小毛病, 因为工具的多样化有可能会呈现以下问题: 因为开发过程中没沟通好, 导致我的项目中引入多个雷同的库, 目前保护的平台的确有这种问题, 以前的开发也是百花齐放呢(怒;
在这里抛出一个问题呀:
在react中, 我能够通过这么写去笼罩组件的props:
const props = { title: '新题目'};<Component title="旧题目" {...props}></Component>
而在vue中用以下的写法却不能笼罩之前组件的props:
<template> <Component title="旧题目" v-bind="{title: '新题目'}"></Component></template>
有看过vue源码的兄台来解答一下纳闷吗? 那么接下来的指标就是去看看vue的源码啦!