作用以及使用
作用:
非路由组件可以通过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);
}
- withRouter是一个高级组件函数,入参是一个组件。通过关键的C函数,返回一个组件,最后调用hoistStatics将组件合并后返回。
- React.createElement(param1,param2,params3)是react创建dom的方法。param1:标签名(div、p..);param2:元素的属性,param3:内部的子元素
- 关键是在创建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);
- 使用history.createBrowserHistory()方法创建histroy、macth等属性,并挂在_this,histroy上;此处的hisotry并不是window.history,是第三方库。
- 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);
- 在render函数中就创建了context.Provider元素,将value值对子元素进行共享,此时history就是BrowserRouter中创建并传递进来的。
- value值对应的就是withRouter中的contex值,所以可以在this.props取的hisrory、match等方法。