说说React-Router底层实现?-面试进阶

React-Router基本了解

对于React-Router是针对React定义的路由库,用于将URL和component进行匹配。

React-Router源码分析

简单前端路由的实现




    
    router


     



上面的路由系统主要由三部分组成

  1. Router.protopyte.init 用于页面初始化(load)/页面url变化 的事件注册
  2. Router.protopyte.route 对路径(address)和回调函数(function)的注册并存放于Router中,为load/hashchange使用
  3. Router.protopyte.refresh 针对不同的路径(address)进行回调的处理

React-Router简单实现




    
    包装方式





其实上诉的操作就是只是针对前端简单路由+historyModule的升级处理。
其中的操作也是类似的。

  1. Router.init(historyModule) ==> Router.protopyte.init
  2. Router.listen(function()) ==> Router.protopyte.route
  3. Router.updateLocation ==> Router.protopyte.refresh

React-Router代码实现分析

由于React-Router版本之间的处理方式有些差别,所以就按最新版本来进行分析。

historyModule(history)的实现

这里针对react-router-dom中的BrowserRouter.js进行分析

import warning from "warning";
import React from "react";
import PropTypes from "prop-types";
import { createBrowserHistory as createHistory } from "history";//这里的history就是上面第二个例子中的historyModule
import Router from "./Router"; //对应第二个例子中的Router对象

/** * The public API for a  that uses HTML5 history. //这里是重点 */
class BrowserRouter extends React.Component {
  history = createHistory(this.props);
  render() {
    return ;
  }
}

export default BrowserRouter;

追踪一下history的实现
文件路径在源码中的history中index.ts

//定义一个接口
export interface History {
    length: number;
    action: Action;
    location: Location;
    push(path: Path, state?: LocationState): void;
    push(location: LocationDescriptorObject): void;
    replace(path: Path, state?: LocationState): void;
    replace(location: LocationDescriptorObject): void;
    go(n: number): void;
    goBack(): void;
    goForward(): void;
    block(prompt?: boolean): UnregisterCallback;
    listen(listener: LocationListener): UnregisterCallback;
    createHref(location: LocationDescriptorObject): Href;
}

除去interface这种类型,是不是对History中定义的属性有点熟悉。参考 前端react面试题详细解答

listen函数的注册

React-Router/Router.js

/** * The public API for putting history on context. //这里的道理类似于例子二中第二步 */
class Router extends React.Component {

  static childContextTypes = {
    router: PropTypes.object.isRequired
  };

  getChildContext() {
    return {
      router: {
        ...this.context.router,
        history: this.props.history,
        route: {
          location: this.props.history.location,
          match: this.state.match
        }
      }
    };
  }

  state = {
    match: this.computeMatch(this.props.history.location.pathname)
  };

  computeMatch(pathname) {
    return {
      path: "/",
      url: "/",
      params: {},
      isExact: pathname === "/"
    };
  }

  componentWillMount() {
    const { children, history } = this.props;
    // Do this here so we can setState when a  changes the
    // location in componentWillMount. This happens e.g. when doing
    // server rendering using a .
    this.unlisten = history.listen(() => {
      this.setState({
        match: this.computeMatch(history.location.pathname)
      });
    });
  }

  componentWillReceiveProps(nextProps) {
    warning(
      this.props.history === nextProps.history,
      "You cannot change "
    );
  }

  componentWillUnmount() {
    this.unlisten();
  }

  render() {
    const { children } = this.props;
    return children ? React.Children.only(children) : null;
  }
}

export default Router;

上面需要有几处需要注意的地方

  1. React-Router是利用React的Context进行组件间通信的。childContextTypes/getChildContext
  2. 需要特别主要componentWillMount,也就是说在Router组件还未加载之前,listen已经被注册。其实这一步和第一个例子中的init道理是类似的。
  3. 在componentWillUnmount中将方法进行注销,用于内存的释放。
  4. 这里提到了 ,其实就是 用于url和组件的匹配。

了解Redirect.js

react-router/Redirect.js

//这里省去其他库的引用
import generatePath from "./generatePath";
/** * The public API for updating the location programmatically * with a component. */
class Redirect extends React.Component {
//这里是从Context中拿到history等数据
  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired
      }).isRequired,
      staticContext: PropTypes.object
    }).isRequired
  };

  isStatic() {
    return this.context.router && this.context.router.staticContext;
  }

  componentWillMount() {
    invariant(
      this.context.router,
      "You should not use  outside a "
    );

    if (this.isStatic()) this.perform();
  }

  componentDidMount() {
    if (!this.isStatic()) this.perform();
  }

  componentDidUpdate(prevProps) {
    const prevTo = createLocation(prevProps.to);
    const nextTo = createLocation(this.props.to);

    if (locationsAreEqual(prevTo, nextTo)) {
      warning(
        false,
        `You tried to redirect to the same route you're currently on: ` +
          `"${nextTo.pathname}${nextTo.search}"`
      );
      return;
    }

    this.perform();
  }

  computeTo({ computedMatch, to }) {
    if (computedMatch) {
      if (typeof to === "string") {
        return generatePath(to, computedMatch.params);
      } else {
        return {
          ...to,
          pathname: generatePath(to.pathname, computedMatch.params)
        };
      }
    }

    return to;
  }
 //进行路由的匹配操作
  perform() {
    const { history } = this.context.router;
    const { push } = this.props;
    //Router中拿到需要跳转的路径,然后传递给history
    const to = this.computeTo(this.props);

    if (push) {
      history.push(to);
    } else {
      history.replace(to);
    }
  }

  render() {
    return null;
  }
}

export default Redirect;

note :

  1. 针对h5的history来讲,push/replace只是将url进行改变,但是不会触发popstate事件

generatePath函数的处理

//该方法只是对路径进行处理
/** * Public API for generating a URL pathname from a pattern and parameters. */
const generatePath = (pattern = "/", params = {}) => {
  if (pattern === "/") {
    return pattern;
  }
  const generator = compileGenerator(pattern);
  return generator(params);
};

针对路径进行页面渲染处理

需要看一个Router的结构

//这里的Router只是一个容器组件,用于从Redux/react中获取数据,而真正的路径/组件信息存放在Route中
 
      
      
      
  

看一下Route对组件的处理

/** * The public API for matching a single path and rendering. */
class Route extends React.Component {
    //从Router中获取信息
  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.object.isRequired,
      route: PropTypes.object.isRequired,
      staticContext: PropTypes.object
    })
  };
//自己定义了一套Contex用于子组件的使用
  static childContextTypes = {
    router: PropTypes.object.isRequired
  };
//自己定义了一套Contex用于子组件的使用
  getChildContext() {
    return {
      router: {
        ...this.context.router,
        route: {
          location: this.props.location || this.context.router.route.location,
          match: this.state.match
        }
      }
    };
  }

  state = {
    match: this.computeMatch(this.props, this.context.router)// matching a URL pathname to a path pattern.如果不匹配,返回null,也就是找不到页面信息
  };
  render() {
    const { match } = this.state;
    const { children, component, render } = this.props;//从Router结构中获取对应的处理方法
    const { history, route, staticContext } = this.context.router;//从Context中获取数据
    const location = this.props.location || route.location;
    const props = { match, location, history, staticContext };
    //如果页面匹配成功,进行createElement的渲染。在这里就会调用component的render===>页面刷新 这是处理第一次页面渲染
    if (component) return match ? React.createElement(component, props) : null;
    //这里针对首页已经被渲染,在进行路由处理的时候,根据props中的信息,进行页面的跳转或者刷新
    if (render) return match ? render(props) : null;

    return null;
  }
}

export default Route;

Buzzer

针对React-Router来讲,其实就是对H5的History进行了一次封装,使能够识别将url的变化与componet渲染进行匹配。

  1. 根据BrowserRouter等不同的API针对H5的history的重构
  2. 结构的构建,同时对history属性进行注册。
  3. 在Router的componentWillMount中注册history的事件回调。
  4. 在Redirect中进行路径的计算,调用history.push/history.replace等更新history信息。
  5. Route中根据计算的匹配结果,进行页面首次渲染/页面更新渲染处理。

你可能感兴趣的:(react.js)