context

在v5路由器组件中,使用context来获取当前正在活跃的location、 history、match等对象。比如historyContext:

  1. // historyContext.js
  2. const createNamedContext = name => {
  3. /*
  4. createContext 函数由“mini-create-react-context” 提供,
  5. 包所作的事情就是对React.createContext的包装,在不存在时提供一个内部实现的context构建函数
  6. */
  7. const context = createContext();
  8. context.displayName = name;
  9. return context;
  10. };
  11. const historyContext = createNamedContext("Router-History");
  12. export default historyContext;
  13. // hook.js
  14. export function useHistory() {
  15. // react.useContext
  16. return useContext(HistoryContext);
  17. }
  18. // router.js
  19. <HistoryContext.Provider
  20. children={this.props.children || null}
  21. value={this.props.history}
  22. />

使用的route组件:

  1. class Route extends React.Component {
  2. render() {
  3. return (
  4. <RouterContext.Consumer>
  5. {context => {
  6. // render
  7. }}
  8. </RouterContext.Consumer>
  9. );
  10. }
  11. }

匹配规则

v5中的匹配规则使用正则来匹配,使用path-to-regexp
在定义路由时会将提供的path转成正则, 与当前location.pathname 进行匹配。

const cache = {};
/*
    cache = {
      key : {
        regexp, keys
    }
  }
*/
// 最大缓存存储数。 如果超过以后的结果不会进入缓存,为此都是实时计算
const cacheLimit = 10000;
let cacheCount = 0;

function compilePath(path, options) {
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  const pathCache = cache[cacheKey] || (cache[cacheKey] = {});

  if (pathCache[path]) return pathCache[path];

  const keys = [];
  const regexp = pathToRegexp(path, keys, options);
  const result = { regexp, keys };

  if (cacheCount < cacheLimit) {
    pathCache[path] = result;
    cacheCount++;
  }

  return result;
}

function matchPath(pathname, options = {}) {
 // 去除参数初始化

  // path 为使用route组件时提供的path属性
  const { path, exact = false, strict = false, sensitive = false } = options;

  const paths = [].concat(path);

  return paths.reduce((matched, path) => {
    if (!path && path !== "") return null;
    // 如果匹配到return
    if (matched) return matched;
        // 根据path转换成正则
    const { regexp, keys } = compilePath(path, {opts});
    const match = regexp.exec(pathname);

    // 不存在匹配的路由
    if (!match) return null;

    const [url, ...values] = match;
    const isExact = pathname === url;

    if (exact && !isExact) return null;

    return {
        // 使用路由组件后的props
    };
    // 初始化为空
  }, null);
}

路由的匹配全部采用正则的形式, router里面只有Route 组件是用来定义路由的

Route对于children、 render、component 的选择:

// 如果Route组件使用时提供的children是一个react数组是不会显示的
if (Array.isArray(children) && isEmptyChildren(children)) {
    children = null;
}


// ------------------------

{
   props.match
    ? children
      ? typeof children === "function"
        ? children(props)
        : children
      : component
        ? React.createElement(component, props)
        : render
          ? render(props)
          : null
    : typeof children === "function"
      ? children(props)
      : null
 }

// 大致的意思就是

function () {

 // 使用pathname 和path转成正则以后匹配的结果为null 就使用children 如果不是函数就是null
  if ( props.match !== null ) {
    if (typeof children === “functin") {
      return children(props);    
    }

    return null;
  }

  if (children !== undefined) {
    if (typeof children === "function") {
      return children(props);
    }
    return children; // children为React.ReactElement类型
  }

  if (component !== undefined) {
    return React.createElment(component, props);
  }

  if (render !== undefined) {
    return render(props)    ;
  }

    return null;
}

匹配的顺序优先级就是 children -> component -> render

和history的结合

react router 依赖history。 而一个应用里面应该只存在一个reactRouter根节点。 一个react应用里面也应该只存在history实例。 假如是多个,路由器信息的改变时,并不会对第二个实例产生影响

// router 组件的构造函数 
function constructor(props) {
    super(props);

    this.state = {
      location: props.history.location
    };

    this._isMounted = false;
    this._pendingLocation = null;

    if (!props.staticContext) {
      // 由props传进来的history实例,选择的一种路由模式。
      // 设置监听, 变化时更新state。 会导致context下, 所有的消费者更新
      this.unlisten = props.history.listen(location => {
        if (this._isMounted) {
          this.setState({ location });
        } else {
          this._pendingLocation = location;
        }
      });
    }
  }

对于browserRouter模式的404问题

React-router-2020 - 图1

如果使用这种模式,出现这种问题由一下几种方式

  1. 开发时 webpackdevserver选项存在对于单页面应用的支持。 去瞅瞅
  2. 在测试或者其他线上环境时,比如nginx做的服务器 ```nginx server { listen 80 default_server; server_name /var/www/example.com; listen 8080;
    root /wwwroot;

    location / {

     root /wwwroot;
     index index.html;
    
     try_files $uri $uri/ /wwwroot/index.html;
    

    }

    root /var/www/example.com; index index.html index.htm;

    location ~* .(?:manifest|appcache|html?|xml|json)$ { expires -1;

    access_log logs/static.log; # I don’t usually include a static log

    }

    location ~* .(?:css|js)$ { try_files $uri =404; expires 1y; access_log off; add_header Cache-Control “public”; }

    location ~ ^.+..+$ { try_files $uri =404; }

     # 最后匹配时,继续返回index.html。 路由的信息由本资源在浏览器计算
    

    location / { try_files $uri $uri/ /index.html; }

} ```