博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React-Router底层原理分析与实现
阅读量:6699 次
发布时间:2019-06-25

本文共 7550 字,大约阅读时间需要 25 分钟。

用技术改变生活,用生活完善技术。来自携程(旅悦)一枚向全栈方向努力奔跑的前端工程师。

微信同步:wDxKn89

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)的实现

import warning from "warning";import React from "react";import PropTypes from "prop-types";import { createBrowserHistory as createHistory } from "history";//这里的history就是上面第二个例子中的historyModuleimport 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中定义的属性有点熟悉。

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的进行组件间通信的。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进行改变,但是不会触发

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中根据计算的匹配结果,进行页面首次渲染/页面更新渲染处理。

转载地址:http://vcwlo.baihongyu.com/

你可能感兴趣的文章
[Ubuntu] Ubuntu如何查看cronjob
查看>>
session监听
查看>>
Android Studio更新升级方法
查看>>
virtualbox主机与虚拟机互访,虚拟机上网
查看>>
ios相关手册、图表等综合
查看>>
SharpZipLib 文件/文件夹压缩
查看>>
百分比定位加position定位的常用布局
查看>>
Codeforces Round #257 (Div. 2/A)/Codeforces450A_Jzzhu and Children
查看>>
android 拍照注意问题
查看>>
form中的button按钮在IE11中自动提交表单问题导致弹出框关闭之后表单被重置
查看>>
在多个游戏视图间切换环境准备
查看>>
Linux命令-权限管理命令:chown
查看>>
第三方推送-使用推
查看>>
Hadoop 2.2.0 HA构造
查看>>
WIN8系统中 任务管理器 性能栏 显示CPU利用率(已暂停)怎么回事?
查看>>
java如何将毫秒数转为相应的年月日格式
查看>>
whu oj 1551 Pairs (莫队算法)
查看>>
BZOJ3424 : Poi2013 Multidrink
查看>>
eclipse 预览Android界面报错
查看>>
iOS:iOS开发系列–打造自己的“美图秀秀”(中)
查看>>