|
| 1 | +# Overview |
| 2 | + |
| 3 | +## Serializing the tree |
| 4 | + |
| 5 | +Every React commit that changes the tree in a way DevTools cares about results in an "_operations_" message being sent across the bridge. These messages are lightweight patches that describe the changes that were made. (We don't resend the full tree structure like in legacy DevTools.) |
| 6 | + |
| 7 | +The payload for each message is a typed array. The first entry is a number identifying which renderer the update belongs to (for multi-root support). The rest of the array depends on the operations being made to the tree. |
| 8 | + |
| 9 | +We only send the following bits of information: element type, id, parent id, owner id, name, and key. Additional information (e.g. props, state) requires a separate "_inspectElement_" message. |
| 10 | + |
| 11 | +### Adding a root node |
| 12 | + |
| 13 | +Adding a root to the tree requires sending 3 numbers: |
| 14 | + |
| 15 | +1. add operation constant (`1`) |
| 16 | +1. fiber id |
| 17 | +1. element type constant (`8 === ElementTypeRoot`) |
| 18 | + |
| 19 | +For example, adding a root fiber with an id of 1: |
| 20 | +```js |
| 21 | +[ |
| 22 | + 1, // add operation |
| 23 | + 1, // fiber id |
| 24 | + 8, // ElementTypeRoot |
| 25 | +] |
| 26 | +``` |
| 27 | + |
| 28 | +### Adding a leaf node |
| 29 | + |
| 30 | +Adding a leaf node takes a variable number of numbers since we need to decode the name (and potentially the key): |
| 31 | + |
| 32 | +1. add operation constant (`1`) |
| 33 | +1. fiber id |
| 34 | +1. element type constant (e.g. `1 === ElementTypeClass`) |
| 35 | +1. parent fiber id |
| 36 | +1. owner fiber id |
| 37 | +1. UTF encoded display name size |
| 38 | + * (followed by this number of encoded values) |
| 39 | +1. UTF encoded key size |
| 40 | + * (followed by this number of encoded values) |
| 41 | + |
| 42 | +For example, adding a function component `<Foo>` with an id 2: |
| 43 | +```js |
| 44 | +[ |
| 45 | + 1, // add operation |
| 46 | + 2, // fiber id |
| 47 | + 2, // ElementTypeFunction |
| 48 | + 1, // parent id |
| 49 | + 0, // owner id |
| 50 | + 3, // encoded display name size |
| 51 | + 70, // "F" |
| 52 | + 111, // "o" |
| 53 | + 111, // "o" |
| 54 | + 0, // encoded key (null) |
| 55 | +] |
| 56 | +``` |
| 57 | + |
| 58 | +### Removing a node |
| 59 | + |
| 60 | +Removing a fiber from the tree (a root or a leaf) only requires sending 2 numbers: |
| 61 | + |
| 62 | +1. remove operation constant (`2`) |
| 63 | +1. fiber id |
| 64 | + |
| 65 | +For example, removing a root fiber with an id of 1: |
| 66 | +```js |
| 67 | +[ |
| 68 | + 2, // remove operation |
| 69 | + 1, // fiber id |
| 70 | +] |
| 71 | +``` |
| 72 | + |
| 73 | +### Re-ordering children |
| 74 | + |
| 75 | +1. re-order children constant (`3`) |
| 76 | +1. fiber id |
| 77 | +1. number of children |
| 78 | + * (followed by an ordered list of child fiber ids) |
| 79 | + |
| 80 | +For example: |
| 81 | +```js |
| 82 | +[ |
| 83 | + 3, // re-order operation |
| 84 | + 15, // fiber id |
| 85 | + 2, // number of children |
| 86 | + 35, // first chlid id |
| 87 | + 21, // second chlid id |
| 88 | +] |
| 89 | +``` |
| 90 | + |
| 91 | +## Reconstructing the tree |
| 92 | + |
| 93 | +The frontend stores its information about the tree in a map of id to objects with the following keys: |
| 94 | + |
| 95 | +* id: `number` |
| 96 | +* parentID: `number` |
| 97 | +* children: `Array<number>` |
| 98 | +* type: `number` (constant) |
| 99 | +* displayName: `string | null` |
| 100 | +* key: `number | string | null` |
| 101 | +* ownerID: `number` |
| 102 | +* depth: `number` <sup>1</sup> |
| 103 | +* weight: `number` <sup>2</sup> |
| 104 | + |
| 105 | +<sup>1</sup> The `weight` of an element is the number of elements (including itself) below it in the tree. We cache this property so that we can quickly determine the total number of Elements as well as to find the Nth element within that set. (This enables us to use windowing.) This value needs to be adjusted each time elements are added or removed from the tree, but we amortize this over time to avoid any big performance hits when rendering the tree. |
| 106 | + |
| 107 | +The `depth` value determines how much padding/indentation to use for the element when rendering it in the Elements panel. (This preserves the appearance of a nested tree, even though the view is a flat list.) |
| 108 | + |
| 109 | +### Finding the element at index N |
| 110 | + |
| 111 | +The tree data structure lets us impose an order on elements and "quickly" find the Nth one using the `weight` attribute. |
| 112 | + |
| 113 | +First we find which root contains the index: |
| 114 | +```js |
| 115 | +let rootID; |
| 116 | +let root; |
| 117 | +let rootWeight = 0; |
| 118 | +for (let i = 0; i < this._roots.length; i++) { |
| 119 | + rootID = this._roots[i]; |
| 120 | + root = this._idToElement.get(rootID); |
| 121 | + if (root.children.length === 0) { |
| 122 | + continue; |
| 123 | + } else if (rootWeight + root.weight > index) { |
| 124 | + break; |
| 125 | + } else { |
| 126 | + rootWeight += root.weight; |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +We skip the root itself because don't display them in the tree: |
| 132 | +```js |
| 133 | +const firstChildID = root.children[0]; |
| 134 | +``` |
| 135 | + |
| 136 | +Then we traverse the tree to find the element: |
| 137 | +```js |
| 138 | +let currentElement = this._idToElement.get(firstChildID); |
| 139 | +let currentWeight = rootWeight; |
| 140 | +while (index !== currentWeight) { |
| 141 | + for (let i = 0; i < currentElement.children.length; i++) { |
| 142 | + const childID = currentElement.children[i]; |
| 143 | + const child = this._idToElement.get(childID); |
| 144 | + const { weight } = child; |
| 145 | + if (index <= currentWeight + weight) { |
| 146 | + currentWeight++; |
| 147 | + currentElement = child; |
| 148 | + break; |
| 149 | + } else { |
| 150 | + currentWeight += weight; |
| 151 | + } |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
0 commit comments