getReactData.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. /**
  12. * Convert a react internal instance to a sanitized data object.
  13. *
  14. * This is shamelessly stolen from react-devtools:
  15. * https://github.com/facebook/react-devtools/blob/master/backend/getData.js
  16. */
  17. function getData(element: Object): Object {
  18. let children = null;
  19. let props = null;
  20. let state = null;
  21. let context = null;
  22. let updater = null;
  23. let name = null;
  24. let type = null;
  25. let text = null;
  26. let publicInstance = null;
  27. let nodeType = 'Native';
  28. // If the parent is a native node without rendered children, but with
  29. // multiple string children, then the `element` that gets passed in here is
  30. // a plain value -- a string or number.
  31. if (typeof element !== 'object') {
  32. nodeType = 'Text';
  33. text = element + '';
  34. } else if (
  35. element._currentElement === null ||
  36. element._currentElement === false
  37. ) {
  38. nodeType = 'Empty';
  39. } else if (element._renderedComponent) {
  40. nodeType = 'NativeWrapper';
  41. children = [element._renderedComponent];
  42. props = element._instance.props;
  43. state = element._instance.state;
  44. context = element._instance.context;
  45. if (context && Object.keys(context).length === 0) {
  46. context = null;
  47. }
  48. } else if (element._renderedChildren) {
  49. children = childrenList(element._renderedChildren);
  50. } else if (element._currentElement && element._currentElement.props) {
  51. // This is a native node without rendered children -- meaning the children
  52. // prop is just a string or (in the case of the <option>) a list of
  53. // strings & numbers.
  54. children = element._currentElement.props.children;
  55. }
  56. if (!props && element._currentElement && element._currentElement.props) {
  57. props = element._currentElement.props;
  58. }
  59. // != used deliberately here to catch undefined and null
  60. if (element._currentElement != null) {
  61. type = element._currentElement.type;
  62. if (typeof type === 'string') {
  63. name = type;
  64. } else if (element.getName) {
  65. nodeType = 'Composite';
  66. name = element.getName();
  67. // 0.14 top-level wrapper
  68. // TODO(jared): The backend should just act as if these don't exist.
  69. if (
  70. element._renderedComponent &&
  71. element._currentElement.props ===
  72. element._renderedComponent._currentElement
  73. ) {
  74. nodeType = 'Wrapper';
  75. }
  76. if (name === null) {
  77. name = 'No display name';
  78. }
  79. } else if (element._stringText) {
  80. nodeType = 'Text';
  81. text = element._stringText;
  82. } else {
  83. name = type.displayName || type.name || 'Unknown';
  84. }
  85. }
  86. if (element._instance) {
  87. const inst = element._instance;
  88. updater = {
  89. setState: inst.setState && inst.setState.bind(inst),
  90. forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst),
  91. setInProps: inst.forceUpdate && setInProps.bind(null, element),
  92. setInState: inst.forceUpdate && setInState.bind(null, inst),
  93. setInContext: inst.forceUpdate && setInContext.bind(null, inst),
  94. };
  95. publicInstance = inst;
  96. // TODO: React ART currently falls in this bucket, but this doesn't
  97. // actually make sense and we should clean this up after stabilizing our
  98. // API for backends
  99. if (inst._renderedChildren) {
  100. children = childrenList(inst._renderedChildren);
  101. }
  102. }
  103. return {
  104. nodeType,
  105. type,
  106. name,
  107. props,
  108. state,
  109. context,
  110. children,
  111. text,
  112. updater,
  113. publicInstance,
  114. };
  115. }
  116. function setInProps(internalInst, path: Array<string | number>, value: any) {
  117. const element = internalInst._currentElement;
  118. internalInst._currentElement = {
  119. ...element,
  120. props: copyWithSet(element.props, path, value),
  121. };
  122. internalInst._instance.forceUpdate();
  123. }
  124. function setInState(inst, path: Array<string | number>, value: any) {
  125. setIn(inst.state, path, value);
  126. inst.forceUpdate();
  127. }
  128. function setInContext(inst, path: Array<string | number>, value: any) {
  129. setIn(inst.context, path, value);
  130. inst.forceUpdate();
  131. }
  132. function setIn(obj: Object, path: Array<string | number>, value: any) {
  133. const last = path.pop();
  134. const parent = path.reduce((obj_, attr) => (obj_ ? obj_[attr] : null), obj);
  135. if (parent) {
  136. parent[last] = value;
  137. }
  138. }
  139. function childrenList(children) {
  140. const res = [];
  141. for (const name in children) {
  142. res.push(children[name]);
  143. }
  144. return res;
  145. }
  146. function copyWithSetImpl(obj, path, idx, value) {
  147. if (idx >= path.length) {
  148. return value;
  149. }
  150. const key = path[idx];
  151. const updated = Array.isArray(obj) ? obj.slice() : {...obj};
  152. // $FlowFixMe number or string is fine here
  153. updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
  154. return updated;
  155. }
  156. function copyWithSet(
  157. obj: Object | Array<any>,
  158. path: Array<string | number>,
  159. value: any,
  160. ): Object | Array<any> {
  161. return copyWithSetImpl(obj, path, 0, value);
  162. }
  163. module.exports = getData;