了解React Route 的源码实现

311 阅读1分钟

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>    );  }}