浅看withRouter

2,011 阅读2分钟

作用以及使用

作用:
非路由组件可以通过withRouter高阶组件访问 History 对象的属性和进行匹配。withRouter将在渲染时向包装组件传递更新的 match、location 和 history 属性。
使用:
app.js

import { withRouter } from 'react-router-dom'

class App extends PureComponent {
  render() { 
    console.log(this.props); //包含match,location,history属性
    
    return (  
      <BrowserRouter>
        <NavLink exact activeClassName='line-active' to='/'>Home</NavLink>
        <NavLink activeClassName='line-active' to='/about'>About</NavLink>
      <Switch>
        <Route exact path='/' component={Home} />
        <Route path='/about' component={About} />
      </Switch>
      </BrowserRouter>
    );
  }
}
 
export default withRouter(App);

index.js

ReactDOM.render(
  // 必须需要使用BrowserRouter或者HashRouter包括
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

追寻withRouter源码

  • 从引入方法import { withRouter } from 'react-router-dom',找到react-router-dom依赖包。
  • 在index.js中
if (process.env.NODE_ENV === "production") {
  module.exports = require("./cjs/react-router-dom.min.js");
} else {
  module.exports = require("./cjs/react-router-dom.js");
}
  • react-router-dom.js
// ...
var reactRouter = require('react-router');
// ...
Object.defineProperty(exports, 'withRouter', {
  enumerable: true,
  get: function () {
    return reactRouter.withRouter;
  }
});
exports.BrowserRouter = BrowserRouter;
exports.HashRouter = HashRouter;
exports.Link = Link;
exports.NavLink = NavLink;

可以看出是利用Object.defineProperty将导出的module对象添加上withRoute,而reactRouter又从react-router引入

  • 按照react-router-dom逻辑寻找;react-router=》index.js=>react-router.js

function withRouter(Component) {
  var displayName = "withRouter(" + (Component.displayName || Component.name) + ")";

  var C = function C(props) {
    var wrappedComponentRef = props.wrappedComponentRef,
        remainingProps = _objectWithoutPropertiesLoose(props, ["wrappedComponentRef"]);

    return /*#__PURE__*/React.createElement(context.Consumer, null, function (context) {
      !context ?  invariant(false, "You should not use <" + displayName + " /> outside a <Router>")  : void 0;
      return /*#__PURE__*/React.createElement(Component, _extends({}, remainingProps, context, {
        ref: wrappedComponentRef
      }));
    });
  };

  C.displayName = displayName;
  C.WrappedComponent = Component;

  {
    C.propTypes = {
      wrappedComponentRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object])
    };
  }

  return hoistStatics(C, Component);
}

源码分析

withRouter方法简化

function withRouter(Component) {

  var C = function C(props) {
    return /*#__PURE__*/React.createElement(context.Consumer, 
                                            null, 
                                            function (context) {
      !context ?  
        invariant(false, "You should not use <" + displayName + " /> outside a <Router>") 
      	: void 0;
      return /*#__PURE__*/React.createElement(Component, _extends({}, remainingProps, context, {
        ref: wrappedComponentRef
      }));
    });
  };

  C.displayName = displayName;
  C.WrappedComponent = Component;

  return hoistStatics(C, Component);
}
  1. withRouter是一个高级组件函数,入参是一个组件。通过关键的C函数,返回一个组件,最后调用hoistStatics将组件合并后返回。
  2. React.createElement(param1,param2,params3)是react创建dom的方法。param1:标签名(div、p..);param2:元素的属性,param3:内部的子元素
  3. 关键是在创建context.Consumer内部,重新创建子元素Component,并且通过_extends方法,将remainingProps, context属性合并,此时history、match等属性就包含在context合并进组件props中。所以context是关键来源。

分析context来源

由于调用了context.Consumer方法,所以context是来自上层组件(BrowserRouter),从react-router-dom.js找到该方法

var BrowserRouter = /*#__PURE__*/function (_React$Component) {
  _inheritsLoose(BrowserRouter, _React$Component);

  function BrowserRouter() {
    var _this;
		// ...
    
    _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
    _this.history = history.createBrowserHistory(_this.props);
    return _this;
  }

  var _proto = BrowserRouter.prototype;

  _proto.render = function render() {
    return /*#__PURE__*/React.createElement(reactRouter.Router, {
      history: this.history,
      children: this.props.children
    });
  };

  return BrowserRouter;
}(React.Component);


  1. 使用history.createBrowserHistory()方法创建histroy、macth等属性,并挂在_this,histroy上;此处的hisotry并不是window.history,是第三方库。
  2. render方式上,创建一个reactRouter.Router元素,并且挂在上histroy属性

reactRouter.Router

从react-router-dom.js中的var reactRouter = require('react-router')中,回到react-router.js文件,找到Router方法

var Router = /*#__PURE__*/function (_React$Component) {
	//...
  function Router(props) {
    var _this;

    _this = _React$Component.call(this, props) || this;
    _this.state = {
      location: props.history.location
    };
		//...
    if (!props.staticContext) {
      _this.unlisten = props.history.listen(function (location) {
        if (_this._isMounted) {
          _this.setState({
            location: location
          });
        } else {
          _this._pendingLocation = location;
        }
      });
    }

    return _this;
  }

  var _proto = Router.prototype;
	//...
  _proto.render = function render() {
    return /*#__PURE__*/React.createElement(context.Provider, {
      value: {
        history: this.props.history,
        location: this.state.location,
        match: Router.computeRootMatch(this.state.location.pathname),
        staticContext: this.props.staticContext
      }
    }, /*#__PURE__*/React.createElement(historyContext.Provider, {
      children: this.props.children || null,
      value: this.props.history
    }));
  };

  return Router;
}(React.Component);
  1. 在render函数中就创建了context.Provider元素,将value值对子元素进行共享,此时history就是BrowserRouter中创建并传递进来的。
  2. value值对应的就是withRouter中的contex值,所以可以在this.props取的hisrory、match等方法。