Preface
It took a while to organize the react-router system, including the functional principles and basic implementation methods of commonly used components. The code posted in the article is the implementation of the core principles of each component, which is slightly different from the source code. Please note that the source address has provided a detailed link, click to jump. Eat with confidence.
Rendering method
- children
- component
- render
priority:
These three rendering methods are mutually exclusive, when they exist at the same time: children
> component
> render
;
This is the priority part of the code in the source code;
Precautions
children
andrender
can only be passed to the node to be displayed in the form ofanonymous function
component
is not required.component
andrender
needpath
after the match to be shown,children
regardless of whether the match will show.component
not recommended to pass in the node to be displayed in the form ofanonymous function
React.createElement
will be called when rendering. If theanonymous function is used, a new type will be generated every time, causing frequent mounting and unmounting of subcomponents. Problem,
children
andrender
will not;
Those who are interested can try to run the code;
'use strict';
import React, { useState, useEffect } from 'react';
import { Router, Route } from 'react-router';
const Child = (props) => {
useEffect(() => {
console.log("挂载");
return () => console.log("卸载");
}, []);
return <div>Child - {props.count}</div>
}
class ChildFunc extends React.Component {
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
render() {
return <div>
ChildFunc - {this.props.count}
</div>
}
}
const Index = (props) => {
const [count, setCount] = useState(0);
return <div>
<button onClick={() => setCount((state) => state + 1)}>add</button>
<p>chick change count{count}</p>
<Router >
{/* bad 观察一下挂载和卸载函数的log*/}
<Route component={() => <Child count={count} />} />
<Route component={() => <ChildFunc count={count} />} />
{/* good 这才是正确的打开方式 观察一下挂载和卸载函数的log*/}
<Route render={() => <Child count={count} />} />
<Route render={() => <ChildFunc count={count} />} />
{/* 观察一下挂载和卸载函数的log 这种也是可以的但是children不需要匹配path,慎用*/}
<Route children={() => <ChildFunc count={count} />} />
<Route children={() => <Child count={count} />} />
</Router>
</div>
};
export default Index;
Link component
The link is essentially an a tag, but when you click directly using the href attribute, there will be jitters and you need to use commands to jump. Some attributes and functions are added to it in the source code, and the parameters to
and click
events are processed.
Source code please move to
'use strict';
import React, { useContext } from 'react'
import RouterContext from './RouterContext'
export default function Link({ to, children }) {
const { history } = useContext(RouterContext)
const handle = e => {
// 防止抖动所以禁掉默认行为命令形式跳转
e.preventDefault();
history.push(to)
};
return <a href={to} onClick={handle}>{children}</a>
};
BrowserRouter component
This component is the react-router
component of 0619e0d328eafb, and its main function is to determine what kind of routing the routing system uses.
View the source code , please move to
'use strict'
import React, { PureComponent } from 'react';
import { createBrowserHistory } from 'history'
import Router from "./Router"
export default class BrowserRouter extends PureComponent {
constructor(props) {
super(props);
this.history = createBrowserHistory();
}
render() {
return <Router history={this.history}>{this.props.children}</Router>
}
};
RouterContext.js file
Because routing components can be nested with ordinary element nodes, and the specific hierarchical relationship cannot be determined well, we still choose the cross-level data to achieve this. Declaring and exporting the RouterContext into separate files will make the logic clearer.
The source code does not directly use createContext
but a layer of createNamedContext
is added to the generated context to add a displayName.
'use strict';
import React from 'react'
const RouterContext = React.createContext();
export default RouterContext;
Router.js file
The main function of the Router file:
- Pass down the attributes such as
history
,location
,match
- Monitor
location
on the page throughhistory.listen
, and passlocation
facilitate theRoute
component and theSwitch
component;
source code
'use strict'
import React, { PureComponent } from 'react';
import RouterContext from 'RouterContext'
export default class Router extends PureComponent {
static computeRootMatch(pathname) {
return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
}
constructor() {
super(props)
this.state = {
location: props.history.location
}
this.unlinsten = props.history.listen((location) => {
this.setState({ location })
})
}
componentWillUnmount() {
this.unlinsten();
}
render() {
return (
<RouterContext.Provider value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRouteMatch(this.state.location.pathname)
}} >
{this.props.children}
</RouterContext.Provider>
)
}
}
Route component
route
component mainly responsible for match
processing and returns need to be rendered component
components, this match
may be the upper Switch
assembly passed down computedMatch
, if the upper layer is not used Switch
component is determined Route
component received path
property exists and which is the location.pathname
is compared. If it matches, it will not be displayed if it is not displayed. path
can also be empty. If it is empty, use context.match
directly;
'use strict'
import React, { PureComponent } from 'react';
import matchPath from './matchPath';
import RouterContext from './RouterContext';
export default class Route extends PureComponent {
render() {
return <RouterContext.Consumer >
{(context) => {
const { path, children, component, render, computedMatch } = this.props;
const { location } = context;
// 当match时,说明当前匹配成功
const match = computedMatch
? computedMatch
: path
? matchPath(location.pathname, this.props)
: context.match;
const props = { ...context, match }
// 匹配成功以后要根据children > component > render的优先级来渲染
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>
}
}
Notice:
- The code repeatedly mentioned
match
is our route to mount the parametersmatch
;- We in the
return
ofcomponent
place to return values are wrapped in a layer ofRouterContext.Provider
, because we use externaluseRouteMatch
anduseParams
getmatch
time,context
acquiredmatch
factRouter.js
file passed down from the initial value, but we are here You need to get thematch
Route
component, so you need to wrap it in one layer, here is the use of thecontext
nearest value of 0619e0d328f047;
switch component
Switch means exclusive routing, and its function: match routing and only render the first route
or redirect
;
For the above reasons, components such as 404 that do not write the path attribute must be placed at the end, otherwise once the 404 component is matched, the subsequent sub-components will no longer be matched;
The difference with the Route component is that the Switch controls which Route component is displayed, and the Route component is empty is whether the component under the current Route component is displayed
'use strict'
import React, { PureComponent } from 'react';
import matchPath from './matchPath';
import RouterContext from './RouterContext';
export default class Switch extends PureComponent {
render() {
return <RouterContext.Consumer>
{
(context) => {
let match; // 标记是否匹配
let element; // 匹配到的元素
/**
* 这里接受到的props.children有可能是一个也有可能是多个
* 理论上我们需要自行去做if判断,但是React提供了一个api,React.Children
* 它当中的forEach会帮助我们完成这样的事情
*/
React.Children.forEach(this.props.children, child => {
// isValidElement判断是不是一个React节点
if (match == null && React.isValidElement(child)) {
element = child;
match = child.props.path
? matchPath(context.location.pathname, child.props)
: context.match
}
});
return match ? React.cloneElement(element, { computedMatch: mactch }) : null
}
}
</RouterContext.Consumer>
}
}
redirect
Redirect is a route redirection, and its function:
- Return an empty component.
- Jump to the execution page
'use strict'
import React, { useEffect, PureComponent } from 'react';
import RouterContext from './RouterContext';
export default class Redirect extends PureComponent {
render() {
return <RouterContext.Consumer>
{
context => {
const { history } = context;
const { to } = this.props;
return <LifeCycle onMount={() => history.push(to)} />
}
}
</RouterContext.Consumer>
}
}
const LifeCycle = ({ onMount }) => {
useEffect(() => {
if (onMount) onMount()
}, [])
return null
}
Several commonly used hooks
Just post the code, I won’t describe these simple ones anymore.
import RouterContext from "./RouterContext";
import {useContext} from "react";
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 is simpler than writing, just set a high-level component, then get the context and pass it in.
Summarize
The knowledge points are basically written in the front, here is a brief summary:
- The BrowserRouter component determines what type of history is used in the routing system at the top level;
- Then define the context in the Router file, use cross-level communication to pass attributes such as history, match, and loaction, and use history.listen to monitor changes in loaction;
- Compare the path and location between the Router component and the Switch component, and render the corresponding components. The Switch component determines which Route component to render, and the Route component determines whether the current component is rendered;
- The Route component has three rendering methods, mutually exclusive and
children
>component
>render
. It is necessary to pay attention to the input standard of the three attributes, and it is not recommended to use the anonymous function method for the component; - Another point to note in Route is to allow us to accurately obtain the match in subsequent use. Here, when
<RouterContext.Provider value={props}> </RouterContext.Provider>
, we need to wrap it with 0619e0d328f3ce and pass in the new match, as well as thenearest value of the context;
- The Switch component implies an exclusive route, that is, only the first Route component that is matched is rendered;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。