上一篇主要是说流程,大概的流程是什么样子的,经历了哪些函数。
本篇开始要具体讲解JSX。如有不对之处还望指出,共同进步,谢谢~
深入理解JSX
JSX对于开发人员应该并不陌生,React也是采用JSX这种方式进行处理、渲染的。
接下来我们思考下面这些问题:
- JSX与Fiber的关系
- React Component 与 React Element之间有什么关系
- React Component、React Element 与 JSX 有什么关系
用 bable 来看看吧
能看到,JSX在React中被编译成 React.createElement 方法的执行。我们来看看 createElement 方法中具体做了什么
createElement
在调用 React.createElement 时,调用的是文件 react/packages/react/src/ReactElement.js 下的 createElement:
export function createElement(type, config, children) { ... }
参数对应:
- type: 'h3'
- config: null
- children: 'hello'
这里的config指JSX对象的属性,比如给h3加入title属性:
如果h3里面还有一个div,第四个参数就会再次调用 React.createElement:
接下来看 React.createElement 方法都做了什么:
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
}
export function createElement(type, config, children) {
// __DEV__ 是用来判断 开发环境 还是 生产环境
// true 是开发环境,false 是生产环境
let propName;
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
// title等属性,不为null
if (config != null) {
// config 是存在ref
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
// key 是否存在
if (hasValidKey(config)) {
key = '' + config.key;
}
// self和source的处理
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 遍历 config
for (propName in config) {
// 除了原型上的属性 和 key ref __self __source 属性,剩下的都放到props属性中
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// 参数数量,length可能是3,也可能是4
const childrenLength = arguments.length - 2;
// 如果是3个参数,props.children直接等于children,hello没有兄弟div的时候。
// 如果是4个参数,props.children被赋值childArray
// childArray 是通过对第四个参数的遍历生成从2开始之后的每一项
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
// 冻结 childArray 数组,不允许再被修改
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
// childArray 赋值给 props.children
props.children = childArray;
}
// type 中是否有默认 defaultProps属性,如果有 遍历给到 props 生成对应的属性
// classComponent 会成为该函数的 type 参数,所以 defaultProps 是 classComponent 的属性。
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);
}
}
}
// 最后返回一个 element
// ReactElement 会返回一个 element
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
你可能会问,bable中有四个参数,但是方法接受三个参数,没关系,你看这段源码里面有arguments,它是通过arguments处理的。
注意:一个classComponent会被当作type参数被传入给 createElement,也就是它的第一个参数。
上面的代码最后会返回ReactElement函数返回的element:
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
// 开发环境会进入这里,生产环境不会进入
// 这段略过,不需要了解
if (__DEV__) {
element._store = {};
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
// 最后返回的就是 element
return element;
};
我们在函数中看到这样一个值:REACT_ELEMENT_TYPE,这个值也出现在 isValidElement,这个函数用来判断什么样的对象是合法的 element。
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
满足return的判断就是合法的element。
还记得我们最初的问题么:
-
React Component 与 React Element之间有什么关系?
React Component是React.createElement 方法的第一个参数参数。React Element就是通过React.createElement 方法调用之后返回的结果。
-
JSX与Fiber之间有什么关系?
首屏渲染时会创建workerInProgress Fiber树,然后会在根节点RootFiber下创建Fiber节点,每个节点其实就是JSX对象。更新时会存在一个current Fiber树,所以在之后生成的workerInProgress Fiber树节点会将组件返回的JSX对象与组件返回的current Fiber节点做对比,根据对比的结果生成workerInProgress Fiber树。
JSX最后会被编译为React.createElement方法的执行,那么理论上来说,只要改变React.createElement方法,就能改变页面上渲染的结果。
总结
本篇文章主要是深入讲解JSX在React中是如何编译的,以及React Component、React Element、JSX之间有什么关系。
接下来会进入mount流程,首屏渲染的具体流程。谢谢大家支持