UNPKG

6.15 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
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 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant');
12
13var DOMProperty = require('./DOMProperty');
14var ReactDOMComponentFlags = require('./ReactDOMComponentFlags');
15
16var invariant = require('fbjs/lib/invariant');
17
18var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
19var Flags = ReactDOMComponentFlags;
20
21var internalInstanceKey = '__reactInternalInstance$' + Math.random().toString(36).slice(2);
22
23/**
24 * Check if a given node should be cached.
25 */
26function shouldPrecacheNode(node, nodeID) {
27 return node.nodeType === 1 && node.getAttribute(ATTR_NAME) === String(nodeID) || node.nodeType === 8 && node.nodeValue === ' react-text: ' + nodeID + ' ' || node.nodeType === 8 && node.nodeValue === ' react-empty: ' + nodeID + ' ';
28}
29
30/**
31 * Drill down (through composites and empty components) until we get a host or
32 * host text component.
33 *
34 * This is pretty polymorphic but unavoidable with the current structure we have
35 * for `_renderedChildren`.
36 */
37function getRenderedHostOrTextFromComponent(component) {
38 var rendered;
39 while (rendered = component._renderedComponent) {
40 component = rendered;
41 }
42 return component;
43}
44
45/**
46 * Populate `_hostNode` on the rendered host/text component with the given
47 * DOM node. The passed `inst` can be a composite.
48 */
49function precacheNode(inst, node) {
50 var hostInst = getRenderedHostOrTextFromComponent(inst);
51 hostInst._hostNode = node;
52 node[internalInstanceKey] = hostInst;
53}
54
55function uncacheNode(inst) {
56 var node = inst._hostNode;
57 if (node) {
58 delete node[internalInstanceKey];
59 inst._hostNode = null;
60 }
61}
62
63/**
64 * Populate `_hostNode` on each child of `inst`, assuming that the children
65 * match up with the DOM (element) children of `node`.
66 *
67 * We cache entire levels at once to avoid an n^2 problem where we access the
68 * children of a node sequentially and have to walk from the start to our target
69 * node every time.
70 *
71 * Since we update `_renderedChildren` and the actual DOM at (slightly)
72 * different times, we could race here and see a newer `_renderedChildren` than
73 * the DOM nodes we see. To avoid this, ReactMultiChild calls
74 * `prepareToManageChildren` before we change `_renderedChildren`, at which
75 * time the container's child nodes are always cached (until it unmounts).
76 */
77function precacheChildNodes(inst, node) {
78 if (inst._flags & Flags.hasCachedChildNodes) {
79 return;
80 }
81 var children = inst._renderedChildren;
82 var childNode = node.firstChild;
83 outer: for (var name in children) {
84 if (!children.hasOwnProperty(name)) {
85 continue;
86 }
87 var childInst = children[name];
88 var childID = getRenderedHostOrTextFromComponent(childInst)._domID;
89 if (childID === 0) {
90 // We're currently unmounting this child in ReactMultiChild; skip it.
91 continue;
92 }
93 // We assume the child nodes are in the same order as the child instances.
94 for (; childNode !== null; childNode = childNode.nextSibling) {
95 if (shouldPrecacheNode(childNode, childID)) {
96 precacheNode(childInst, childNode);
97 continue outer;
98 }
99 }
100 // We reached the end of the DOM children without finding an ID match.
101 !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Unable to find element with ID %s.', childID) : _prodInvariant('32', childID) : void 0;
102 }
103 inst._flags |= Flags.hasCachedChildNodes;
104}
105
106/**
107 * Given a DOM node, return the closest ReactDOMComponent or
108 * ReactDOMTextComponent instance ancestor.
109 */
110function getClosestInstanceFromNode(node) {
111 if (node[internalInstanceKey]) {
112 return node[internalInstanceKey];
113 }
114
115 // Walk up the tree until we find an ancestor whose instance we have cached.
116 var parents = [];
117 while (!node[internalInstanceKey]) {
118 parents.push(node);
119 if (node.parentNode) {
120 node = node.parentNode;
121 } else {
122 // Top of the tree. This node must not be part of a React tree (or is
123 // unmounted, potentially).
124 return null;
125 }
126 }
127
128 var closest;
129 var inst;
130 for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
131 closest = inst;
132 if (parents.length) {
133 precacheChildNodes(inst, node);
134 }
135 }
136
137 return closest;
138}
139
140/**
141 * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
142 * instance, or null if the node was not rendered by this React.
143 */
144function getInstanceFromNode(node) {
145 var inst = getClosestInstanceFromNode(node);
146 if (inst != null && inst._hostNode === node) {
147 return inst;
148 } else {
149 return null;
150 }
151}
152
153/**
154 * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
155 * DOM node.
156 */
157function getNodeFromInstance(inst) {
158 // Without this first invariant, passing a non-DOM-component triggers the next
159 // invariant for a missing parent, which is super confusing.
160 !(inst._hostNode !== undefined) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getNodeFromInstance: Invalid argument.') : _prodInvariant('33') : void 0;
161
162 if (inst._hostNode) {
163 return inst._hostNode;
164 }
165
166 // Walk up the tree until we find an ancestor whose DOM node we have cached.
167 var parents = [];
168 while (!inst._hostNode) {
169 parents.push(inst);
170 !inst._hostParent ? process.env.NODE_ENV !== 'production' ? invariant(false, 'React DOM tree root should always have a node reference.') : _prodInvariant('34') : void 0;
171 inst = inst._hostParent;
172 }
173
174 // Now parents contains each ancestor that does *not* have a cached native
175 // node, and `inst` is the deepest ancestor that does.
176 for (; parents.length; inst = parents.pop()) {
177 precacheChildNodes(inst, inst._hostNode);
178 }
179
180 return inst._hostNode;
181}
182
183var ReactDOMComponentTree = {
184 getClosestInstanceFromNode: getClosestInstanceFromNode,
185 getInstanceFromNode: getInstanceFromNode,
186 getNodeFromInstance: getNodeFromInstance,
187 precacheChildNodes: precacheChildNodes,
188 precacheNode: precacheNode,
189 uncacheNode: uncacheNode
190};
191
192module.exports = ReactDOMComponentTree;
\No newline at end of file