class Swith extends React.Component{
static contextType = RouterContext
render(){
const {context}= this;
const {children}=this.props;
console.log(children);
const {location} = context;
let element,match;
React.Children.forEach(children,child=>{
//child $$typeof == Symbol('react.element')
if(React.isValidElement(child)){//如果此节点是一个React元素
if(!match){//如果尚未有任何元素匹配
element = child;
match = matchPath(location.pathname,child.props);
}
}
});
return match?React.cloneElement(element,{computedMatch:match}):null;
}
}
class Route extends React.Component{
static contextType = RouterContext;
render(){
const {history,location} = this.context;
const {path,component:RouteComponent,exact=false,computedMatch} = this.props;
//const match = exact?location.pathname===path:location.pathname.startsWith(path);// /user /user
//const match = matchPath(location.pathname,this.props);
const match = computedMatch?computedMatch:matchPath(location.pathname,this.props);
const routeProps = {history,location};
let renderElement=null;// null也一个合法的react渲染节点 代表我们render的返顺值,代表此组件将要渲染的内容
if(match){
routeProps.match=match;
//React.createElement(RouteComponent,routeProps);
renderElement = <RouteComponent {...routeProps}/>
}
return renderElement
}
}
Switch和Route通常是嵌套关系,例如:
<Switch>
<Route path="/" component={Home} exact={true}/>
<Route path="/user" component={User}/>
<Route path="/profile" component={Profile}/>
<Redirect to="/"/>
</Switch>
Switch中会根据当前的location从children中过滤出来匹配的项进行渲染,但Route本身也可以单独使用,所以它内部也有获取当前location,再判断Route是否匹配这个location,这部分逻辑是重复的,所以Switch会将computedMatch这样一个属性传给Route
通常在最后会放一个Redirect组件,用于前面所有Route都匹配不到时跳转到一个指定路径来用
function Redirect({to}){
return (
<RouterContext.Consumer>
{
value=>{
const {history} = value;
/* history.push(to);
return null; */
return <Lifecycle onMount={()=>history.push(to)}/>
}
}
</RouterContext.Consumer>
)
}
export default Redirect;
import React from 'react';
class Lifecycle extends React.Component{
componentDidMount(){
if(this.props.onMount)
this.props.onMount(this);
}
render(){
return null;
}
}
export default Lifecycle;
Link:
import React from 'react'
import {__RouterContext as RouterContext} from '../react-router';
export default function Link(props){
return (
<RouterContext.Consumer>
{
value=>{
return (
<a
{...props}
onClick={(event)=>{
event.preventDefault();
value.history.push(props.to);
}}
>{props.children}</a>
)
}
}
</RouterContext.Consumer>
)
}
如果想让一个普通组件获得history这样的路由组件才有的属性,则需要使用withRouter包装一个高阶组件:
例如,希望下面的NavHeader组件有history属性:
class NavHeader extends Component {
render() {
return (
<div onClick={()=>this.props.history.push('/')}>
{this.props.title}
</div>
)
}
}
export default withRouter(NavHeader);
就可以通过withRouter包装
import {Route} from './';
export default function withRouter(OldComponent) {
//TODO
return (
(props)=><Route render={
routeProps=><OldComponent {...routeProps} {...props}/>
}/>
)
}
源码中withRouter的实现方式:
function withRouter(OldComponent) {
function NewComponent(props){
return (
<RouterContext.Consumer>
{
value=>{
return <OldComponent {...value} {...props}/>
}
}
</RouterContext.Consumer>
)
}
return NewComponent
}
export default withRouter;
hooks:
import React from 'react';
import RouterContext from './RouterContext';
import matchPath from './matchPath';
export function useParams(){
let match = React.useContext(RouterContext).match;
return match?match.params:{};
}
export function useHistory(){
return React.useContext(RouterContext).history;
}
export function useLocation(){
return React.useContext(RouterContext).location;
}
export function useRouteMatch(options){//TODO
const location = useLocation();
return matchPath(location.pathname,options);
}