创建 React 元素
React.createElement
/*** 创建 React Element* type 元素类型* config 配置属性* children 子元素* 1. 分离 props 属性和特殊属性* 2. 将子元素挂载到 props.children 中* 3. 为 props 属性赋默认值 (defaultProps)* 4. 创建并返回 ReactElement*/export function createElement(type, config, children) {/*** propName -> 属性名称* 用于后面的 for 循环*/let propName;/*** 存储 React Element 中的普通元素属性 即不包含 key ref self source*/const props = {};/*** 待提取属性* React 内部为了实现某些功能而存在的属性*/let key = null;let ref = null;let self = null;let source = null;// 如果 config 不为 nullif (config != null) {// 如果 config 对象中有合法的 ref 属性if (hasValidRef(config)) {// 将 config.ref 属性提取到 ref 变量中ref = config.ref;// 在开发环境中if (__DEV__) {// 如果 ref 属性的值被设置成了字符串形式就报一个提示// 说明此用法在将来的版本中会被删除warnIfStringRefCannotBeAutoConverted(config);}}// 如果在 config 对象中拥有合法的 key 属性if (hasValidKey(config)) {// 将 config.key 属性中的值提取到 key 变量中key = '' + config.key;}self = config.__self === undefined ? null : config.__self;source = config.__source === undefined ? null : config.__source;// 遍历 config 对象for (propName in config) {// 如果当前遍历到的属性是对象自身属性// 并且在 RESERVED_PROPS 对象中不存在该属性if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {// 将满足条件的属性添加到 props 对象中 (普通属性)props[propName] = config[propName];}}}/*** 将第三个及之后的参数挂载到 props.children 属性中* 如果子元素是多个 props.children 是数组* 如果子元素是一个 props.children 是对象*/// 由于从第三个参数开始及以后都表示子元素// 所以减去前两个参数的结果就是子元素的数量const childrenLength = arguments.length - 2;// 如果子元素的数量是 1if (childrenLength === 1) {// 直接将子元素挂载到到 props.children 属性上// 此时 children 是对象类型props.children = children;// 如果子元素的数量大于 1} else if (childrenLength > 1) {// 创建数组, 数组中元素的数量等于子元素的数量const childArray = Array(childrenLength);// 开启循环 循环次匹配子元素的数量for (let i = 0; i < childrenLength; i++) {// 将子元素添加到 childArray 数组中// i + 2 的原因是实参集合的前两个参数不是子元素childArray[i] = arguments[i + 2];}// 如果是开发环境if (__DEV__) {// 如果 Object 对象中存在 freeze 方法if (Object.freeze) {// 调用 freeze 方法 冻结 childArray 数组// 防止 React 核心对象被修改 冻结对象提高性能Object.freeze(childArray);}}// 将子元素数组挂载到 props.children 属性中props.children = childArray;}/*** 如果当前处理是组件* 看组件身上是否有 defaultProps 属性* 这个属性中存储的是 props 对象中属性的默认值* 遍历 defaultProps 对象 查看对应的 props 属性的值是否为 undefined* 如果为undefined 就将默认值赋值给对应的 props 属性值*/// 将 type 属性值视为函数 查看其中是否具有 defaultProps 属性if (type && type.defaultProps) {// 将 type 函数下的 defaultProps 属性赋值给 defaultProps 变量const defaultProps = type.defaultProps;// 遍历 defaultProps 对象中的属性 将属性名称赋值给 propName 变量for (propName in defaultProps) {// 如果 props 对象中的该属性的值为 undefinedif (props[propName] === undefined) {// 将 defaultProps 对象中的对应属性的值赋值给 props 对象中的对应属性的值props[propName] = defaultProps[propName];}}}/*** 在开发环境中 如果元素的 key 属性 或者 ref 属性存在* 监测开发者是否在组件内部通过 props 对象获取了 key 属性或者 ref 属性* 如果获取了 就报错*/// 如果处于开发环境if (__DEV__) {// 元素具有 key 属性或者 ref 属性if (key || ref) {// 看一下 type 属性中存储的是否是函数 如果是函数就表示当前元素是组件// 如果元素不是组件 就直接返回元素类型字符串// displayName 用于在报错过程中显示是哪一个组件报错了// 如果开发者显式定义了 displayName 属性 就显示开发者定义的// 否者就显示组件名称 如果组件也没有名称 就显示 'Unknown'const displayName =typeof type === 'function'? type.displayName || type.name || 'Unknown': type;// 如果 key 属性存在if (key) {// 为 props 对象添加key 属性// 并指定当通过 props 对象获取 key 属性时报错defineKeyPropWarningGetter(props, displayName);}// 如果 ref 属性存在if (ref) {// 为 props 对象添加 ref 属性// 并指定当通过 props 对象获取 ref 属性时报错defineRefPropWarningGetter(props, displayName);}}}// 返回 ReactElementreturn ReactElement(type,key,ref,self,source,// 在 Virtual DOM 中用于识别自定义组件ReactCurrentOwner.current,props,);}
ReactElement
/*** 接收参数 返回 ReactElement*/const ReactElement = function (type, key, ref, self, source, owner, props) {const element = {/*** 组件的类型, 十六进制数值或者 Symbol 值* React 在最终在渲染 DOM 的时候, 需要确保元素的类型是 REACT_ELEMENT_TYPE* 需要此属性作为判断的依据*/$$typeof: REACT_ELEMENT_TYPE,/*** 元素具体的类型值 如果是元素节点 type 属性中存储的就是 div span 等等* 如果元素是组件 type 属性中存储的就是组件的构造函数*/type: type,/*** 元素的唯一标识* 用作内部 vdom 比对 提升 DOM 操作性能*/key: key,/*** 存储元素 DOM 对象或者组件 实例对象*/ref: ref,/*** 存储向组件内部传递的数据*/props: props,/*** 记录当前元素所属组件 (记录当前元素是哪个组件创建的)*/_owner: owner,};// 返回 ReactElementreturn element;};
hasValidRef
/*** 查看参数对象中是否有合法的 ref 属性* 返回布尔值*/function hasValidRef(config) {return config.ref !== undefined;}
hasValidKey
/*** 查看参数对象中是否有合法的 key 属性* 返回布尔值*/function hasValidKey(config) {return config.key !== undefined;}
defineKeyPropWarningGetter
/*** 指定当通过 props 对象获取 key 属性时报错* props 组件中的 props 对象* displayName 组件名称标识*/function defineKeyPropWarningGetter(props, displayName) {// 通过 props 对象获取 key 属性报错const warnAboutAccessingKey = function () {// 在开发环境中if (__DEV__) {// specialPropKeyWarningShown 控制错误只输出一次的变量if (!specialPropKeyWarningShown) {// 通过 specialPropKeyWarningShown 变量锁住判断条件specialPropKeyWarningShown = true;// 指定报错信息和组件名称console.error('%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://reactjs.org/link/special-props)',displayName,);}}};warnAboutAccessingKey.isReactWarning = true;// 为 props 对象添加 key 属性Object.defineProperty(props, 'key', {// 当获取 key 属性时调用 warnAboutAccessingKey 方法进行报错get: warnAboutAccessingKey,configurable: true,});}
defineRefPropWarningGetter
/*** 指定当通过 props 对象获取 ref 属性时报错* props 组件中的 props 对象* displayName 组件名称标识*/function defineRefPropWarningGetter(props, displayName) {// 通过 props 对象获取 ref 属性报错const warnAboutAccessingRef = function () {if (__DEV__) {// specialPropRefWarningShown 控制错误只输出一次的变量if (!specialPropRefWarningShown) {// 通过 specialPropRefWarningShown 变量锁住判断条件specialPropRefWarningShown = true;// 指定报错信息和组件名称console.error('%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://reactjs.org/link/special-props)',displayName,);}}};warnAboutAccessingRef.isReactWarning = true;// 为 props 对象添加 key 属性Object.defineProperty(props, 'ref', {get: warnAboutAccessingRef,configurable: true,});}
React.isValidElement
验证 object 参数是否是有效的 ReactElement
/*** 验证 object 参数是否是 ReactElement. 返回布尔值* 验证成功的条件:* object 是对象* object 不为null* object对象中的 $$typeof 属性值为 REACT_ELEMENT_TYPE*/export function isValidElement(object) {return (typeof object === 'object' &&object !== null &&object.$$typeof === REACT_ELEMENT_TYPE);}
