169 lines
5.0 KiB
JavaScript
169 lines
5.0 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule getReactData
|
|
* @flow
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* Convert a react internal instance to a sanitized data object.
|
|
*
|
|
* This is shamelessly stolen from react-devtools:
|
|
* https://github.com/facebook/react-devtools/blob/master/backend/getData.js
|
|
*/
|
|
function getData(element: Object): Object {
|
|
var children = null;
|
|
var props = null;
|
|
var state = null;
|
|
var context = null;
|
|
var updater = null;
|
|
var name = null;
|
|
var type = null;
|
|
var text = null;
|
|
var publicInstance = null;
|
|
var nodeType = 'Native';
|
|
// If the parent is a native node without rendered children, but with
|
|
// multiple string children, then the `element` that gets passed in here is
|
|
// a plain value -- a string or number.
|
|
if (typeof element !== 'object') {
|
|
nodeType = 'Text';
|
|
text = element + '';
|
|
} else if (element._currentElement === null || element._currentElement === false) {
|
|
nodeType = 'Empty';
|
|
} else if (element._renderedComponent) {
|
|
nodeType = 'NativeWrapper';
|
|
children = [element._renderedComponent];
|
|
props = element._instance.props;
|
|
state = element._instance.state;
|
|
context = element._instance.context;
|
|
if (context && Object.keys(context).length === 0) {
|
|
context = null;
|
|
}
|
|
} else if (element._renderedChildren) {
|
|
children = childrenList(element._renderedChildren);
|
|
} else if (element._currentElement && element._currentElement.props) {
|
|
// This is a native node without rendered children -- meaning the children
|
|
// prop is just a string or (in the case of the <option>) a list of
|
|
// strings & numbers.
|
|
children = element._currentElement.props.children;
|
|
}
|
|
|
|
if (!props && element._currentElement && element._currentElement.props) {
|
|
props = element._currentElement.props;
|
|
}
|
|
|
|
// != used deliberately here to catch undefined and null
|
|
if (element._currentElement != null) {
|
|
type = element._currentElement.type;
|
|
if (typeof type === 'string') {
|
|
name = type;
|
|
} else if (element.getName) {
|
|
nodeType = 'Composite';
|
|
name = element.getName();
|
|
// 0.14 top-level wrapper
|
|
// TODO(jared): The backend should just act as if these don't exist.
|
|
if (element._renderedComponent && element._currentElement.props === element._renderedComponent._currentElement) {
|
|
nodeType = 'Wrapper';
|
|
}
|
|
if (name === null) {
|
|
name = 'No display name';
|
|
}
|
|
} else if (element._stringText) {
|
|
nodeType = 'Text';
|
|
text = element._stringText;
|
|
} else {
|
|
name = type.displayName || type.name || 'Unknown';
|
|
}
|
|
}
|
|
|
|
if (element._instance) {
|
|
var inst = element._instance;
|
|
updater = {
|
|
setState: inst.setState && inst.setState.bind(inst),
|
|
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst),
|
|
setInProps: inst.forceUpdate && setInProps.bind(null, element),
|
|
setInState: inst.forceUpdate && setInState.bind(null, inst),
|
|
setInContext: inst.forceUpdate && setInContext.bind(null, inst),
|
|
};
|
|
publicInstance = inst;
|
|
|
|
// TODO: React ART currently falls in this bucket, but this doesn't
|
|
// actually make sense and we should clean this up after stabilizing our
|
|
// API for backends
|
|
if (inst._renderedChildren) {
|
|
children = childrenList(inst._renderedChildren);
|
|
}
|
|
}
|
|
|
|
return {
|
|
nodeType,
|
|
type,
|
|
name,
|
|
props,
|
|
state,
|
|
context,
|
|
children,
|
|
text,
|
|
updater,
|
|
publicInstance,
|
|
};
|
|
}
|
|
|
|
function setInProps(internalInst, path: Array<string | number>, value: any) {
|
|
var element = internalInst._currentElement;
|
|
internalInst._currentElement = {
|
|
...element,
|
|
props: copyWithSet(element.props, path, value),
|
|
};
|
|
internalInst._instance.forceUpdate();
|
|
}
|
|
|
|
function setInState(inst, path: Array<string | number>, value: any) {
|
|
setIn(inst.state, path, value);
|
|
inst.forceUpdate();
|
|
}
|
|
|
|
function setInContext(inst, path: Array<string | number>, value: any) {
|
|
setIn(inst.context, path, value);
|
|
inst.forceUpdate();
|
|
}
|
|
|
|
function setIn(obj: Object, path: Array<string | number>, value: any) {
|
|
var last = path.pop();
|
|
var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj);
|
|
if (parent) {
|
|
parent[last] = value;
|
|
}
|
|
}
|
|
|
|
function childrenList(children) {
|
|
var res = [];
|
|
for (var name in children) {
|
|
res.push(children[name]);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function copyWithSetImpl(obj, path, idx, value) {
|
|
if (idx >= path.length) {
|
|
return value;
|
|
}
|
|
var key = path[idx];
|
|
var updated = Array.isArray(obj) ? obj.slice() : {...obj};
|
|
// $FlowFixMe number or string is fine here
|
|
updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
|
|
return updated;
|
|
}
|
|
|
|
function copyWithSet(obj: Object | Array<any>, path: Array<string | number>, value: any): Object | Array<any> {
|
|
return copyWithSetImpl(obj, path, 0, value);
|
|
}
|
|
|
|
module.exports = getData;
|