React-router是我们开发过程中,经常使用的库。在不接触源码的基础上,我们可能会对默认写法,props接收到的值产生疑惑。这片文章就是为你解惑的。
问:HashRouter
和BrowserRouter
异同?
答:相同点是底层都依赖于react-router
中的Router
组件,都是通过history
库创建history
对象作为参数传给Router组件。
import { createHashHistory as createHistory } from "history";
import { Router } from "react-router";
class HashRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
差别如上可见,调用了history
库中不同的方法创建的history
对象,而且不仅仅如此,暴露出去的props
也不太一样:
HashRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
getUserConfirmation: PropTypes.func,
hashType: PropTypes.oneOf(["hashbang", "noslash", "slash"])
};
BrowserRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number
};
问:Route组件的props为什么能拿到history,location和match对象?
答:Router
使用了context
,创建了Provider
,将location
,history
,match
和staticContext
作为内容传给Consumer
。而Route
作为context
的Consumer
,就可以接受到参数了。
// Router.js
import React from "react";
import HistoryContext from './HistoryContext'
import RouterContext from './RouterContext'
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
location: props.history.location
};
// 监听路由变化。处于mount过程中,有<Redirect>组件导致路由发生变化,
// 为了防止location丢失,以及页面抖动(setState)
// 这时需要把变化的location放到内存中,等页面mount后,再setState
this._isMounted = false;
this._pendingLocation = null;
if (!props.staticContext) {
this.unlisten = props.history.listen(location => {
// 如果页面mount成功,直接setState;
// 如果页面还在mounting,则等待页面mount后再setState
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
}
}
componentDidMount() {
this._isMounted = true;
if (this._pendingLocation) {
this.setState({ location: this._pendingLocation });
}
}
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
this._isMounted = false;
this._pendingLocation = null;
}
}
render() {
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
}
// Route.js
import React from "react";
class Route extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const location = this.props.location || context.location;
// <Switch>组件会传入computedMatch
// 其他方式是不会传入computedMatch字段的
const match = this.props.computedMatch
? this.props.computedMatch
: (
// matchPath:如果是完全匹配的路由,返回{path, url, isExact, params}
// 如果不匹配,返回null。这行是控制路由组件展示的关键
this.props.path ? matchPath(location.pathname, this.props) : context.match
);
const props = { ...context, location, match };
let { children, component, render } = this.props;
if (Array.isArray(children) && React.Children.count(children) === 0) {
children = null;
}
return (
<RouterContext.Provider value={props}>
{
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>
);
}
}
// createNamedContext.js
const createNamedContext = name => {
const context = React.createContext();
context.displayName = name;
return context;
}
// HistoryContext.js
const HistoryContext = createNamedContext("Router-History")
export default HistoryContext
// RouterContext.js
const RouterContext = createNamedContext("Router")
export default RouterContext
因此,如下问题的发生,就不难解释了。未匹配到/c路由,但是执行了/c路由下的方法。
可通过Switch
包裹Route
组件避免此种情况的发生。
问:Switch组件是如何保证渲染的是第一个路由组件
答:通过遍历子组件,取出第一个满足pathname的路由组件,clone后展示出来。
import React from "react";
class Switch extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const location = this.props.location || context.location;
let element, match;
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;
}
});
return match
? React.cloneElement(element, { location, computedMatch: match })
: null;
}}
</RouterContext.Consumer>
);
}
}
问:高级组件withRouter如何将location等传入普通组件内
答:withRouter内部订阅了context的consumer,将context的包含的值作为props传入原组件中。
import React from "react";
import hoistStatics from "hoist-non-react-statics";
function withRouter(Component) {
const C = props => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<RouterContext.Consumer>
{context => {
return (
<Component
{...remainingProps}
{...context}
ref={wrappedComponentRef}
/>
);
}}
</RouterContext.Consumer>
);
};
C.WrappedComponent = Component;
// 拷贝Component上非React的静态方法,防止静态方法丢失
return hoistStatics(C, Component);
}
问:router自带的hook有哪些,原理是啥?
答:Hook底层均是对useContext钩子的封装,通过useContext钩子获取context的值。Hook有如下4种:useHistory
,useLocation
,useParams
,useRouteMatch
import { useContext } from 'react'
import RouterContext from "./RouterContext"
import HistoryContext from "./HistoryContext"
export function useHistory() {
return useContext(HistoryContext)
}
export function useLocation() {
return useContext(RouterContext).location
}
export function useParams() {
const match = useContext(RouterContext).match
return match ? match.params : {}
}
export function useRouteMatch(path) {
const location = useLocation();
const match = useContext(RouterContext).match
return path ? matchPath(location.pathname, path) : match
}
相关链接:
hoist-non-react-statics:zhuanlan.zhihu.com/p/36178509
React.Children: segmentfault.com/a/119000001…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!