parseReactElement.js.flow 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /* @flow */
  2. import React, { type Element as ReactElement, Fragment } from 'react';
  3. import {
  4. ForwardRef,
  5. isContextConsumer,
  6. isContextProvider,
  7. isForwardRef,
  8. isLazy,
  9. isMemo,
  10. isProfiler,
  11. isStrictMode,
  12. isSuspense,
  13. Memo,
  14. } from 'react-is';
  15. import type { Options } from './../options';
  16. import {
  17. createStringTreeNode,
  18. createNumberTreeNode,
  19. createReactElementTreeNode,
  20. createReactFragmentTreeNode,
  21. } from './../tree';
  22. import type { TreeNode } from './../tree';
  23. const supportFragment = Boolean(Fragment);
  24. const getFunctionTypeName = (functionType): string => {
  25. if (!functionType.name || functionType.name === '_default') {
  26. return 'No Display Name';
  27. }
  28. return functionType.name;
  29. };
  30. const getWrappedComponentDisplayName = (Component: *): string => {
  31. switch (true) {
  32. case Boolean(Component.displayName):
  33. return Component.displayName;
  34. case Component.$$typeof === Memo:
  35. return getWrappedComponentDisplayName(Component.type);
  36. case Component.$$typeof === ForwardRef:
  37. return getWrappedComponentDisplayName(Component.render);
  38. default:
  39. return getFunctionTypeName(Component);
  40. }
  41. };
  42. // heavily inspired by:
  43. // https://github.com/facebook/react/blob/3746eaf985dd92f8aa5f5658941d07b6b855e9d9/packages/react-devtools-shared/src/backend/renderer.js#L399-L496
  44. const getReactElementDisplayName = (element: ReactElement<*>): string => {
  45. switch (true) {
  46. case typeof element.type === 'string':
  47. return element.type;
  48. case typeof element.type === 'function':
  49. if (element.type.displayName) {
  50. return element.type.displayName;
  51. }
  52. return getFunctionTypeName(element.type);
  53. case isForwardRef(element):
  54. case isMemo(element):
  55. return getWrappedComponentDisplayName(element.type);
  56. case isContextConsumer(element):
  57. return `${element.type._context.displayName || 'Context'}.Consumer`;
  58. case isContextProvider(element):
  59. return `${element.type._context.displayName || 'Context'}.Provider`;
  60. case isLazy(element):
  61. return 'Lazy';
  62. case isProfiler(element):
  63. return 'Profiler';
  64. case isStrictMode(element):
  65. return 'StrictMode';
  66. case isSuspense(element):
  67. return 'Suspense';
  68. default:
  69. return 'UnknownElementType';
  70. }
  71. };
  72. const noChildren = (propsValue, propName) => propName !== 'children';
  73. const onlyMeaningfulChildren = (children): boolean =>
  74. children !== true &&
  75. children !== false &&
  76. children !== null &&
  77. children !== '';
  78. const filterProps = (originalProps: {}, cb: (any, string) => boolean) => {
  79. const filteredProps = {};
  80. Object.keys(originalProps)
  81. .filter(key => cb(originalProps[key], key))
  82. .forEach(key => (filteredProps[key] = originalProps[key]));
  83. return filteredProps;
  84. };
  85. const parseReactElement = (
  86. element: ReactElement<*> | string | number,
  87. options: Options
  88. ): TreeNode => {
  89. const { displayName: displayNameFn = getReactElementDisplayName } = options;
  90. if (typeof element === 'string') {
  91. return createStringTreeNode(element);
  92. } else if (typeof element === 'number') {
  93. return createNumberTreeNode(element);
  94. } else if (!React.isValidElement(element)) {
  95. throw new Error(
  96. `react-element-to-jsx-string: Expected a React.Element, got \`${typeof element}\``
  97. );
  98. }
  99. const displayName = displayNameFn(element);
  100. const props = filterProps(element.props, noChildren);
  101. if (element.ref !== null) {
  102. props.ref = element.ref;
  103. }
  104. const key = element.key;
  105. if (typeof key === 'string' && key.search(/^\./)) {
  106. // React automatically add key=".X" when there are some children
  107. props.key = key;
  108. }
  109. const defaultProps = filterProps(element.type.defaultProps || {}, noChildren);
  110. const childrens = React.Children.toArray(element.props.children)
  111. .filter(onlyMeaningfulChildren)
  112. .map(child => parseReactElement(child, options));
  113. if (supportFragment && element.type === Fragment) {
  114. return createReactFragmentTreeNode(key, childrens);
  115. }
  116. return createReactElementTreeNode(
  117. displayName,
  118. props,
  119. defaultProps,
  120. childrens
  121. );
  122. };
  123. export default parseReactElement;