源码阅读-react-router-dom
前言
通过本文可以学到如下高级知识
- 模块原理(babel转换原理)
- 源码阅读与跟踪方式
- 逗号表达式的应用
- 原生方法写React组件
- React中history在外部使用
React项目中react-router也是非常重要的一部分, 但由于用法简单可以参照官网案例使用, 一般配置好就很少改动.
在react-router V3中提供browserHistory给我们在react组件外部使用, 如:
// 在入口文件使用router时指定history, 和routes配置
import { Router, browserHistory } from 'react-router'
import routes from './app/routes'
render(<Router history={browserHistory} routes={routes}/>, el)
// 在外部使用
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
而react-router V4中取消了browserHistory, 但使用redux做为状态管理的项目需要在组件外部使用history功能, 此时可以使用connected-react-router
做中间处理. 用history/createBrowserHistory
自己创建一个history代替组件react-router-dom的BrowserRouter
.
connected-react-router的使用可以看官方示例, 有很好的说明, 此博文是为了记录在看react-router-dom/BrowserRouter.js
的代码时的心得.
过程
找到代码文件
- 进入项目依赖包中的react-router-dom:
cd node_modules/react-router-dom/
- 通过package.js中的main字段找到入口文件:
index.js
- 打开
./index.js
文件光标移到到最后可以发现很多变量挂载到exports中, 找到exports.BrowserRouter = _BrowserRouter3.default;
, 根据_BrowserRouter3找到var _BrowserRouter2 = require('./BrowserRouter'); var _BrowserRouter3 = _interopRequireDefault(_BrowserRouter2);
, 至此找到BrowserRouter的代码文件为./BrowserRouter
, 其中有两点
-
第五行的
exports.__esModule = true;
作用是为了标记这个模块符合babel规范, babel将es6的代码转换成es5的代码, 就会做一个这样的标记, babel再次遇到就会知道, 这是它转换后的代码. -
var _MemoryRouter3 = _interopRequireDefault(_MemoryRouter2);
代码中的_interopRequireDefault函数的意思是检查导入的模块是否符合babel的规范, 这个函数的代码如下:function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
其实就是babel将ES6的代码转换成ES5, nodejs的模块引入规范是CommonJS而ES6是自己的Module实现(包含CommonJS), 所以需要babel做一层中间的代码转换
利用https://babeljs.io/repl
网站可以做代码的对比分析
// 模块导出
// ES 6
export const InlineExport = { }
const NormalExport = { }
const RenameExport = { }
const DefaultExport = { }
export { NormalExport }
export { RenameExport as HasRenamed }
export default DefaultExport
// 转换成ES 5后
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var InlineExport = exports.InlineExport = {};
var NormalExport = {};
var RenameExport = {};
var DefaultExport = {};
exports.NormalExport = NormalExport;
exports.HasRenamed = RenameExport;
exports.default = DefaultExport;
// 模块导入
// ES 6
mport { NormalExport } from 'normal'
import { HasRenamed as RenameAgain } from 'rename'
import DefaultExport from 'default'
import * as All from 'all'
NormalExport()
RenameAgain()
DefaultExport()
All()
// 转换成ES 5后
'use strict';
var _normal = require('normal');
var _rename = require('rename');
var _default = require('default');
var _default2 = _interopRequireDefault(_default);
var _all = require('all');
var All = _interopRequireWildcard(_all);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
(0, _normal.NormalExport)();
(0, _rename.HasRenamed)();
(0, _default2.default)();
All();
代码分析
通过上一步已经可以找到代码文件了, 打开代码文件可以找到如下代码
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _createBrowserHistory = require('history/createBrowserHistory');
var _createBrowserHistory2 = _interopRequireDefault(_createBrowserHistory);
function _inherits(subClass, superClass) {
// subClass 继承 superClass
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(
superClass && superClass.prototype, {
constructor: {
value: subClass, enumerable: false, writable: true, configurable: true
}
}
);
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var BrowserRouter = function (_React$Component) {
// React组件的原生写法
_inherits(BrowserRouter, _React$Component);
function BrowserRouter() {
var _temp, _this, _ret;
// _classCallCheck(this, BrowserRouter);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (
_temp = (
_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))),
_this
),
_this.history = (0, _createBrowserHistory2.default)(_this.props),
_temp
), _possibleConstructorReturn(_this, _ret);
}
BrowserRouter.prototype.componentWillMount = function componentWillMount() {
(0, _warning2.default)(!this.props.history, '<BrowserRouter> ignores the history prop. To use a custom history, ' + 'use `import { Router }` instead of `import { BrowserRouter as Router }`.');
};
BrowserRouter.prototype.render = function render() {
return _react2.default.createElement(
_Router2.default,
{ history: this.history, children: this.props.children }
);
};
return BrowserRouter;
}(_react2.default.Component);
BrowserRouter.propTypes = {
basename: _propTypes2.default.string,
forceRefresh: _propTypes2.default.bool,
getUserConfirmation: _propTypes2.default.func,
keyLength: _propTypes2.default.number,
children: _propTypes2.default.node
};
exports.default = BrowserRouter;
- 以上代码有如下需要关注的技术点
- 原生写法写react组件
- 函数之间的继承
- 逗号表达式的使用
我们关注下逗号表达式的使用, 提取代码:
return _ret = (
_temp = (
_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))),
_this
),
_this.history = (0, _createBrowserHistory2.default)(_this.props),
_temp
), _possibleConstructorReturn(_this, _ret);
关于逗号表达式我们要知道当使用逗号表达式作为返回值时始终返回左到右的最后一个表达式结果, 如console.log((0, 1, 'ans'));
, 输出: ans
因此(0, func)(params)
的写法”等同于”func(params)
, 那为什么不直接用func(params)
, 其实就是因为两种方法执行时内部this的不同.
实验验证
- 定义函数
var obj = {
getThis: function () {
"use strict";
return this;
},
};
var newObj = obj.getThis;
进行如下调用:
console.log(obj.getThis() === obj)
=>true
console.log(newObj())
=>undefined
console.log(newObj() === obj)
=>false
console.log(obj.getThis())
=>{ getThis: [Function: getThis] }
console.log((obj.getThis)())
=>{ getThis: [Function: getThis] }
console.log((0, obj.getThis)())
=>undefined
结论
obj.getThis是引用类型, 赋值给newObj时, newObj不是引用类型此时this丢失, 因此返回this时为undefined.
(0, func)(params)
这种写法避免this污染, 等同于先赋值在执行函数var newObj = obj.getThis; newObj();
, 或绑定全局thisfunc.bind(GlobalThis)
参考文献与工具
- Why is (0,obj.prop)() not a method call? 🔗
- babel:issues#3917 🔗
- 析 Babel 转换 ES6 module 的原理 🔗
- babel在线代码转换 🔗