创建 React 元素

  1. React.createElement

    1. /**
    2. * 创建 React Element
    3. * type 元素类型
    4. * config 配置属性
    5. * children 子元素
    6. * 1. 分离 props 属性和特殊属性
    7. * 2. 将子元素挂载到 props.children 中
    8. * 3. 为 props 属性赋默认值 (defaultProps)
    9. * 4. 创建并返回 ReactElement
    10. */
    11. export function createElement(type, config, children) {
    12. /**
    13. * propName -> 属性名称
    14. * 用于后面的 for 循环
    15. */
    16. let propName;
    17. /**
    18. * 存储 React Element 中的普通元素属性 即不包含 key ref self source
    19. */
    20. const props = {};
    21. /**
    22. * 待提取属性
    23. * React 内部为了实现某些功能而存在的属性
    24. */
    25. let key = null;
    26. let ref = null;
    27. let self = null;
    28. let source = null;
    29. // 如果 config 不为 null
    30. if (config != null) {
    31. // 如果 config 对象中有合法的 ref 属性
    32. if (hasValidRef(config)) {
    33. // 将 config.ref 属性提取到 ref 变量中
    34. ref = config.ref;
    35. // 在开发环境中
    36. if (__DEV__) {
    37. // 如果 ref 属性的值被设置成了字符串形式就报一个提示
    38. // 说明此用法在将来的版本中会被删除
    39. warnIfStringRefCannotBeAutoConverted(config);
    40. }
    41. }
    42. // 如果在 config 对象中拥有合法的 key 属性
    43. if (hasValidKey(config)) {
    44. // 将 config.key 属性中的值提取到 key 变量中
    45. key = '' + config.key;
    46. }
    47. self = config.__self === undefined ? null : config.__self;
    48. source = config.__source === undefined ? null : config.__source;
    49. // 遍历 config 对象
    50. for (propName in config) {
    51. // 如果当前遍历到的属性是对象自身属性
    52. // 并且在 RESERVED_PROPS 对象中不存在该属性
    53. if (
    54. hasOwnProperty.call(config, propName) &&
    55. !RESERVED_PROPS.hasOwnProperty(propName)
    56. ) {
    57. // 将满足条件的属性添加到 props 对象中 (普通属性)
    58. props[propName] = config[propName];
    59. }
    60. }
    61. }
    62. /**
    63. * 将第三个及之后的参数挂载到 props.children 属性中
    64. * 如果子元素是多个 props.children 是数组
    65. * 如果子元素是一个 props.children 是对象
    66. */
    67. // 由于从第三个参数开始及以后都表示子元素
    68. // 所以减去前两个参数的结果就是子元素的数量
    69. const childrenLength = arguments.length - 2;
    70. // 如果子元素的数量是 1
    71. if (childrenLength === 1) {
    72. // 直接将子元素挂载到到 props.children 属性上
    73. // 此时 children 是对象类型
    74. props.children = children;
    75. // 如果子元素的数量大于 1
    76. } else if (childrenLength > 1) {
    77. // 创建数组, 数组中元素的数量等于子元素的数量
    78. const childArray = Array(childrenLength);
    79. // 开启循环 循环次匹配子元素的数量
    80. for (let i = 0; i < childrenLength; i++) {
    81. // 将子元素添加到 childArray 数组中
    82. // i + 2 的原因是实参集合的前两个参数不是子元素
    83. childArray[i] = arguments[i + 2];
    84. }
    85. // 如果是开发环境
    86. if (__DEV__) {
    87. // 如果 Object 对象中存在 freeze 方法
    88. if (Object.freeze) {
    89. // 调用 freeze 方法 冻结 childArray 数组
    90. // 防止 React 核心对象被修改 冻结对象提高性能
    91. Object.freeze(childArray);
    92. }
    93. }
    94. // 将子元素数组挂载到 props.children 属性中
    95. props.children = childArray;
    96. }
    97. /**
    98. * 如果当前处理是组件
    99. * 看组件身上是否有 defaultProps 属性
    100. * 这个属性中存储的是 props 对象中属性的默认值
    101. * 遍历 defaultProps 对象 查看对应的 props 属性的值是否为 undefined
    102. * 如果为undefined 就将默认值赋值给对应的 props 属性值
    103. */
    104. // 将 type 属性值视为函数 查看其中是否具有 defaultProps 属性
    105. if (type && type.defaultProps) {
    106. // 将 type 函数下的 defaultProps 属性赋值给 defaultProps 变量
    107. const defaultProps = type.defaultProps;
    108. // 遍历 defaultProps 对象中的属性 将属性名称赋值给 propName 变量
    109. for (propName in defaultProps) {
    110. // 如果 props 对象中的该属性的值为 undefined
    111. if (props[propName] === undefined) {
    112. // 将 defaultProps 对象中的对应属性的值赋值给 props 对象中的对应属性的值
    113. props[propName] = defaultProps[propName];
    114. }
    115. }
    116. }
    117. /**
    118. * 在开发环境中 如果元素的 key 属性 或者 ref 属性存在
    119. * 监测开发者是否在组件内部通过 props 对象获取了 key 属性或者 ref 属性
    120. * 如果获取了 就报错
    121. */
    122. // 如果处于开发环境
    123. if (__DEV__) {
    124. // 元素具有 key 属性或者 ref 属性
    125. if (key || ref) {
    126. // 看一下 type 属性中存储的是否是函数 如果是函数就表示当前元素是组件
    127. // 如果元素不是组件 就直接返回元素类型字符串
    128. // displayName 用于在报错过程中显示是哪一个组件报错了
    129. // 如果开发者显式定义了 displayName 属性 就显示开发者定义的
    130. // 否者就显示组件名称 如果组件也没有名称 就显示 'Unknown'
    131. const displayName =
    132. typeof type === 'function'
    133. ? type.displayName || type.name || 'Unknown'
    134. : type;
    135. // 如果 key 属性存在
    136. if (key) {
    137. // 为 props 对象添加key 属性
    138. // 并指定当通过 props 对象获取 key 属性时报错
    139. defineKeyPropWarningGetter(props, displayName);
    140. }
    141. // 如果 ref 属性存在
    142. if (ref) {
    143. // 为 props 对象添加 ref 属性
    144. // 并指定当通过 props 对象获取 ref 属性时报错
    145. defineRefPropWarningGetter(props, displayName);
    146. }
    147. }
    148. }
    149. // 返回 ReactElement
    150. return ReactElement(
    151. type,
    152. key,
    153. ref,
    154. self,
    155. source,
    156. // 在 Virtual DOM 中用于识别自定义组件
    157. ReactCurrentOwner.current,
    158. props,
    159. );
    160. }

    ReactElement

    1. /**
    2. * 接收参数 返回 ReactElement
    3. */
    4. const ReactElement = function (type, key, ref, self, source, owner, props) {
    5. const element = {
    6. /**
    7. * 组件的类型, 十六进制数值或者 Symbol 值
    8. * React 在最终在渲染 DOM 的时候, 需要确保元素的类型是 REACT_ELEMENT_TYPE
    9. * 需要此属性作为判断的依据
    10. */
    11. $$typeof: REACT_ELEMENT_TYPE,
    12. /**
    13. * 元素具体的类型值 如果是元素节点 type 属性中存储的就是 div span 等等
    14. * 如果元素是组件 type 属性中存储的就是组件的构造函数
    15. */
    16. type: type,
    17. /**
    18. * 元素的唯一标识
    19. * 用作内部 vdom 比对 提升 DOM 操作性能
    20. */
    21. key: key,
    22. /**
    23. * 存储元素 DOM 对象或者组件 实例对象
    24. */
    25. ref: ref,
    26. /**
    27. * 存储向组件内部传递的数据
    28. */
    29. props: props,
    30. /**
    31. * 记录当前元素所属组件 (记录当前元素是哪个组件创建的)
    32. */
    33. _owner: owner,
    34. };
    35. // 返回 ReactElement
    36. return element;
    37. };

    hasValidRef

    1. /**
    2. * 查看参数对象中是否有合法的 ref 属性
    3. * 返回布尔值
    4. */
    5. function hasValidRef(config) {
    6. return config.ref !== undefined;
    7. }

    hasValidKey

    1. /**
    2. * 查看参数对象中是否有合法的 key 属性
    3. * 返回布尔值
    4. */
    5. function hasValidKey(config) {
    6. return config.key !== undefined;
    7. }

    defineKeyPropWarningGetter

    1. /**
    2. * 指定当通过 props 对象获取 key 属性时报错
    3. * props 组件中的 props 对象
    4. * displayName 组件名称标识
    5. */
    6. function defineKeyPropWarningGetter(props, displayName) {
    7. // 通过 props 对象获取 key 属性报错
    8. const warnAboutAccessingKey = function () {
    9. // 在开发环境中
    10. if (__DEV__) {
    11. // specialPropKeyWarningShown 控制错误只输出一次的变量
    12. if (!specialPropKeyWarningShown) {
    13. // 通过 specialPropKeyWarningShown 变量锁住判断条件
    14. specialPropKeyWarningShown = true;
    15. // 指定报错信息和组件名称
    16. console.error(
    17. '%s: `key` is not a prop. Trying to access it will result ' +
    18. 'in `undefined` being returned. If you need to access the same ' +
    19. 'value within the child component, you should pass it as a different ' +
    20. 'prop. (https://reactjs.org/link/special-props)',
    21. displayName,
    22. );
    23. }
    24. }
    25. };
    26. warnAboutAccessingKey.isReactWarning = true;
    27. // 为 props 对象添加 key 属性
    28. Object.defineProperty(props, 'key', {
    29. // 当获取 key 属性时调用 warnAboutAccessingKey 方法进行报错
    30. get: warnAboutAccessingKey,
    31. configurable: true,
    32. });
    33. }

    defineRefPropWarningGetter

    1. /**
    2. * 指定当通过 props 对象获取 ref 属性时报错
    3. * props 组件中的 props 对象
    4. * displayName 组件名称标识
    5. */
    6. function defineRefPropWarningGetter(props, displayName) {
    7. // 通过 props 对象获取 ref 属性报错
    8. const warnAboutAccessingRef = function () {
    9. if (__DEV__) {
    10. // specialPropRefWarningShown 控制错误只输出一次的变量
    11. if (!specialPropRefWarningShown) {
    12. // 通过 specialPropRefWarningShown 变量锁住判断条件
    13. specialPropRefWarningShown = true;
    14. // 指定报错信息和组件名称
    15. console.error(
    16. '%s: `ref` is not a prop. Trying to access it will result ' +
    17. 'in `undefined` being returned. If you need to access the same ' +
    18. 'value within the child component, you should pass it as a different ' +
    19. 'prop. (https://reactjs.org/link/special-props)',
    20. displayName,
    21. );
    22. }
    23. }
    24. };
    25. warnAboutAccessingRef.isReactWarning = true;
    26. // 为 props 对象添加 key 属性
    27. Object.defineProperty(props, 'ref', {
    28. get: warnAboutAccessingRef,
    29. configurable: true,
    30. });
    31. }
  2. React.isValidElement

验证 object 参数是否是有效的 ReactElement

  1. /**
  2. * 验证 object 参数是否是 ReactElement. 返回布尔值
  3. * 验证成功的条件:
  4. * object 是对象
  5. * object 不为null
  6. * object对象中的 $$typeof 属性值为 REACT_ELEMENT_TYPE
  7. */
  8. export function isValidElement(object) {
  9. return (
  10. typeof object === 'object' &&
  11. object !== null &&
  12. object.$$typeof === REACT_ELEMENT_TYPE
  13. );
  14. }