1、Router 的实现
从源码中可以看出,Router是基于Context 实现的,Router用于传递history。
import React from "react";import PropTypes from "prop-types";import warning from "tiny-warning";import HistoryContext from "./HistoryContext.js";import RouterContext from "./RouterContext.js";class Router extends React.Component { static computeRootMatch(pathname) { return { path: "/", url: "/", params: {}, isExact: pathname === "/" }; } constructor(props) { ... } componentDidMount() { ... } componentWillUnmount() { ... } 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> ); }}export default Router;
HistoryContext、RouterContext 是基于mini-create-react-context 实现的。
// RouteContext.js
import createNamedContext from "./createNameContext";const context = /*#__PURE__*/ createNamedContext("Router");export default context;
// HistoryContext.js
import createNamedContext from "./createNameContext";const historyContext = /*#__PURE__*/ createNamedContext("Router-History");export default historyContext;
// createNameContext.jsimport createContext from "mini-create-react-context";const createNamedContext = name => { const context = createContext(); context.displayName = name; return context;};export default createNamedContext;
2、Route 的实现
Route用于匹配单个path后渲染组件。从源码中可以看出,Route 是基于RouterContext.Consumer 实现的。
props.match 匹配后的渲染的优先级是:children > component > render。
1、判断 children 是不是function
是function,执行 children(props) 并返回值
不是function,返回 children
2、判断 component,返回 React.createElement(component, props)
3、判断 render ,返回 render(props)
4、没有收到children、component、render,返回null
props.match 不匹配,判断 typeof children === "function",是function ,执行 children(props) 并返回值,不是function,返回 null
class Route extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Route> outside a <Router>"); const location = this.props.location || context.location; const match = this.props.computedMatch ? this.props.computedMatch // <Switch> already computed the match for us : this.props.path ? matchPath(location.pathname, this.props) : context.match; const props = { ...context, location, match }; let { children, component, render } = this.props; // Preact uses an empty array as children by // default, so use null if that's the case. if (Array.isArray(children) && isEmptyChildren(children)) { children = null; } 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> ); }}
3、Switch
Switch用于渲染第一个匹配的Route。
class Switch extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Switch> outside a <Router>"); const location = this.props.location || context.location; let element, match; // 找到第一个匹配的child
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> ); }}