序
学习源码的目的是为了更好得解决问题,越接近本质就越能解决复杂问题。同时读 React 源码是在汲取世界上最顶尖的前端工程师的养分,提升编码水平。
源码版本 16.7.0
1. JSX 与 关注点分离
JSX 简介,以上是 React 官方对 JSX 的介绍。
使用 React 最常用的就是使用 JSX 语法了。JSX 让 HTML 标签以及生成这些标签的代码内在得紧密联系在一起,于是我们不需要把单个组件拆分成视图和模板文件,反而是为每一个小的关注点创造一个独立的组件,并且把所有的逻辑和标签封装在其中。
这种设计原则叫做 “关注点分离”,目的是为了更有效得理解,设计和管理有许多功能互相依存的复杂系统,以便功能可以重用,独立于其他功能进行优化。
举个例子:配电将炉子保持在一个电路,而灯光则保持在另一个电路上,这样炉子的超载就不会影响灯光。
当然JSX 的优点还有很多,包括让代码更加直观,对代码的抽象能力……
2. JSX 是如何运行的?
<div id="id">stone</div>
// 以下是 JSX 代码 用 Babel 转化出来的原生 JavaScript 代码。
"use strict";
/*#__PURE__*/
React.createElement("div", {
id: "id"
}, "stone");
我们可以看到 JSX 语法的实质上是用 React 的 API —— createElement 创造节点。
它有三个参数,第 1 个参数是节点的类型,如果是原生的标签会是字符串;第 2 个参数是一个对象,包含了所有节点上的属性;第 3 个参数是节点内的内容。
再看一个复杂的例子。
function Comp() {
return <a>123</a>
}
<Comp id="id">
<span></span>
<span></span>
<div>stone</div>
</Comp>
// 以下是 JSX 代码用 Babel 转化出来的原生 JavaScript 代码。
"use strict";
function Comp() {
return /*#__PURE__*/React.createElement("a", null, "123");
}
/*#__PURE__*/
React.createElement(Comp, {
id: "id"
},
/*#__PURE__*/React.createElement("span", null),
/*#__PURE__*/React.createElement("span", null),
/*#__PURE__*/React.createElement("div", null, "stone"));
如上图所示,组件在转化为原生 JavaScript 后,名称变成了一个变量,同时组件内部的多个标签转化为了,第 4,第 5,第 6个参数。
function stone() {
return <a>Stone</a>
}
<stone id="id">
<span></span>
<span></span>
<div>stone</div>
</stone>
// 以下是 JSX 代码用 Babel 转化出来的原生 JavaScript 代码。
"use strict";
function stone() {
return /*#__PURE__*/React.createElement("a", null, "Stone");
}
/*#__PURE__*/
React.createElement("stone", {
id: "id"
}, /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement("div", null, "stone"));
要注意的是,如果组件名称是小写会被转化成 字符串,从而在运行时发生错误。
3、createElement 实现
现在我们知道 JSX 的实现要依赖于 createElement,首先我们找到 React 的入口文件 React.js。
目录然后在 React.js 中找到目标 createElement 函数的文件 ReactElement。
React.js
以下是源码,以及我的部分注释,可以看到 createElement如何处理这 3 个参数,设计理念与部分细节后续补充。
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
// 1、type,原生节点会传入字符串,如果是组件就会传入一个变量,并且组件首字母需要大写。
// 2、节点属性都会存到config中
// 3、children就是标签的内容
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
// 在属性中找到合理的合理的Ref 和合理的 key
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
// Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。
// const hasOwnProperty = Object.prototype.hasOwnProperty;
// 判断config对象中是否有 ref 属性
// function hasValidRef(config) {
// if (__DEV__) {
// if (hasOwnProperty.call(config, 'ref')) {
// const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
// if (getter && getter.isReactWarning) {
// return false;
// }
// }
// }
// return config.ref !== undefined;
// }
// function hasValidKey(config) {
// if (__DEV__) {
// if (hasOwnProperty.call(config, 'key')) {
// const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
// if (getter && getter.isReactWarning) {
// return false;
// }
// }
// }
// return config.key !== undefined;
// }
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
// const RESERVED_PROPS = {
// key: true,
// ref: true,
// __self: true,
// __source: true,
// };
// 以上是定义的内嵌属性
// this.props中是不会有这些内嵌属性的,因为在这里就把他们处理掉了
// 判断属性是否是内嵌的属性,如果不是就放到一个props对象里。如果不是就不会放
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
// children 是可以有多个的,后续的参数被认为是children
// 用argument.length - 2的长度来代表 children的长度
// 如果长度是1就把这个 chilren直接放进去
// 如果长度 > 1,就会组成一个数组 包含所有的children
// 然后吧这个数组放到props.children属性中
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
// 生成一个长度为children长度的数组
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
// 设置组件默认值
// Comp.defaultProps = { value: 1};
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 判断原生节点还是组件
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
// function defineKeyPropWarningGetter(props, displayName) {
// const warnAboutAccessingKey = function() {
// if (!specialPropKeyWarningShown) {
// specialPropKeyWarningShown = true;
// warningWithoutStack(
// false,
// '%s: `key` is not a prop. Trying to access it will result ' +
// 'in `undefined` being returned. If you need to access the same ' +
// 'value within the child component, you should pass it as a different ' +
// 'prop. (https://fb.me/react-special-props)',
// displayName,
// );
// }
// };
// warnAboutAccessingKey.isReactWarning = true;
// Object.defineProperty(props, 'key', {
// get: warnAboutAccessingKey,
// configurable: true,
// });
// }
// function defineRefPropWarningGetter(props, displayName) {
// const warnAboutAccessingRef = function() {
// if (!specialPropRefWarningShown) {
// specialPropRefWarningShown = true;
// warningWithoutStack(
// false,
// '%s: `ref` is not a prop. Trying to access it will result ' +
// 'in `undefined` being returned. If you need to access the same ' +
// 'value within the child component, you should pass it as a different ' +
// 'prop. (https://fb.me/react-special-props)',
// displayName,
// );
// }
// };
// warnAboutAccessingRef.isReactWarning = true;
// Object.defineProperty(props, 'ref', {
// get: warnAboutAccessingRef,
// configurable: true,
// });
// }
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
参考资料:
1、源码分析手册
2、React源码深度解析 高级前端工程师必备技能
3、百度百科
4、React 官方文档
网友评论