@samitouri / QOS-React-2 / commits / 2794b92164

Add some support for reordering

Dan Abramov committed May 31, 2019 at 18:00 UTC 2794b92164058f38683cad49f04ff801e04ff34b
3 files changed +166 -11
src/__tests__/legacy/__snapshots__/storeLegacy-v15-test.js.snap
+79
@@ -15,6 +15,32 @@ exports[`Store (legacy) collapseNodesByDefault:false should filter DOM nodes fro
15 <div>
16 `;
17
18 +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 1: mount 1`] = `
19 +[root]
20 + ▾ <Root>
21 + ▾ <div>
22 + ▾ <Component key="a">
23 + <div>
24 +`;
25 +
26 +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 2: add child 1`] = `
27 +[root]
28 + ▾ <Root>
29 + ▾ <div>
30 + ▾ <Component key="a">
31 + <div>
32 + ▾ <Component key="b">
33 + <div>
34 +`;
35 +
36 +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 3: remove child 1`] = `
37 +[root]
38 + ▾ <Root>
39 + ▾ <div>
40 + ▾ <Component key="b">
41 + <div>
42 +`;
43 +
44 exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 1: mount 1`] = `
45 [root]
46 ▾ <Grandparent>
@@ -185,6 +211,59 @@ exports[`Store (legacy) collapseNodesByDefault:false should support mount and up
211
212 exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations: 3: unmount 1`] = ``;
213
214 +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 1: mount 1`] = `
215 +[root]
216 + ▾ <Root>
217 + ▾ <div>
218 + ▾ <Foo key="foo">
219 + ▾ <div>
220 + ▾ <Component key="0">
221 + <div>
222 + ▾ <Bar key="bar">
223 + ▾ <div>
224 + ▾ <Component key="0">
225 + <div>
226 + ▾ <Component key="1">
227 + <div>
228 +`;
229 +
230 +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 2: reorder children 1`] = `
231 +[root]
232 + ▾ <Root>
233 + ▾ <div>
234 + ▾ <Bar key="bar">
235 + ▾ <div>
236 + ▾ <Component key="0">
237 + <div>
238 + ▾ <Component key="1">
239 + <div>
240 + ▾ <Foo key="foo">
241 + ▾ <div>
242 + ▾ <Component key="0">
243 + <div>
244 +`;
245 +
246 +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 3: collapse root 1`] = `
247 +[root]
248 + ▸ <Root>
249 +`;
250 +
251 +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 4: expand root 1`] = `
252 +[root]
253 + ▾ <Root>
254 + ▾ <div>
255 + ▾ <Bar key="bar">
256 + ▾ <div>
257 + ▾ <Component key="0">
258 + <div>
259 + ▾ <Component key="1">
260 + <div>
261 + ▾ <Foo key="foo">
262 + ▾ <div>
263 + ▾ <Component key="0">
264 + <div>
265 +`;
266 +
267 exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 1: mount 1`] = `
268 [root]
269 ▸ <Grandparent>
src/__tests__/legacy/storeLegacy-v15-test.js
+5 -8
@@ -176,10 +176,9 @@ describe('Store (legacy)', () => {
176 expect(store).toMatchSnapshot('6: expand Grandparent');
177 });
178
179 - // TODO Re-enable this test once the renderer supports it.
180 - xit('should support adding and removing children', () => {
179 + it('should support adding and removing children', () => {
180 const Root = ({ children }) => <div>{children}</div>;
182 - const Component = () => null;
181 + const Component = () => <div />;
182
183 const container = document.createElement('div');
184
@@ -215,10 +214,9 @@ describe('Store (legacy)', () => {
214 expect(store).toMatchSnapshot('3: remove child');
215 });
216
218 - // TODO Re-enable this test once the renderer supports it.
219 - xit('should support reordering of children', () => {
217 + it('should support reordering of children', () => {
218 const Root = ({ children }) => <div>{children}</div>;
221 - const Component = () => null;
219 + const Component = () => <div />;
220
221 const Foo = () => <div>{[<Component key="0" />]}</div>;
222 const Bar = () => (
@@ -447,10 +445,9 @@ describe('Store (legacy)', () => {
445 expect(store).toMatchSnapshot('6: expand middle node');
446 });
447
450 - // TODO Re-enable this test once the renderer supports it.
448 xit('should support reordering of children', () => {
449 const Root = ({ children }) => <div>{children}</div>;
453 - const Component = () => null;
450 + const Component = () => <div />;
451
452 const Foo = () => <div>{[<Component key="0" />]}</div>;
453 const Bar = () => (
src/backend/legacy/renderer.js
+82 -3
@@ -12,6 +12,7 @@ import {
12 __DEBUG__,
13 TREE_OPERATION_ADD,
14 TREE_OPERATION_REMOVE,
15 + TREE_OPERATION_REORDER_CHILDREN,
16 } from '../../constants';
17 import getChildren from './getChildren';
18 import getData from './getData';
@@ -49,6 +50,10 @@ export function attach(
50 const idToInternalInstanceMap: Map<number, InternalInstance> = new Map();
51 const idToParentIDMap: Map<number, number> = new Map();
52 const internalInstanceToIDMap: Map<InternalInstance, number> = new Map();
53 + const internalInstanceToLastKnownChildrenMap: WeakMap<
54 + InternalInstance,
55 + Array<number>
56 + > = new WeakMap();
57 const rootIDs: Set<number> = new Set();
58
59 function getID(internalInstance: InternalInstance): number {
@@ -217,6 +222,7 @@ export function attach(
222 const result = fn.apply(this, args);
223 currentParentID = prevParentID;
224
225 + recordPendingReorder(internalInstance);
226 return result;
227 },
228 receiveComponent(fn, args) {
@@ -227,6 +233,7 @@ export function attach(
233 const result = fn.apply(this, args);
234 currentParentID = prevParentID;
235
236 + recordPendingReorder(internalInstance);
237 return result;
238 },
239 unmountComponent(fn, args) {
@@ -261,6 +268,7 @@ export function attach(
268
269 const pendingMountIDs: Set<number> = new Set();
270 const pendingUnmountIDs: Set<number> = new Set();
271 + const pendingReorderIDs: Set<number> = new Set();
272 const pendingOperations: Array<number> = [];
273 const pendingStringTable: Map<string, number> = new Map();
274 let pendingStringTableLength: number = 0;
@@ -303,7 +311,11 @@ export function attach(
311 // It should be possible to improve this though, by maintaining a map of id-to-parent,
312 // and crawling upward to the first non-filtered node.
313 // TODO Revisit this and think about it more...
306 - if (pendingMountIDs.size > 0 || pendingUnmountIDs.size > 0) {
314 + if (
315 + pendingMountIDs.size > 0 ||
316 + pendingUnmountIDs.size > 0 ||
317 + pendingReorderIDs.size > 0
318 + ) {
319 rootIDs.forEach(flushPendingEvents);
320 }
321 }, 0);
@@ -396,6 +408,7 @@ export function attach(
408 const numUnmountIDs =
409 unmountIDs.length + (pendingUnmountedRootID === null ? 0 : 1);
410
411 + const reorderOperations = computePendingReorderOperations();
412 const operations = new Uint32Array(
413 // Identify which renderer this update is coming from.
414 2 + // [rendererID, rootFiberID]
@@ -406,8 +419,12 @@ export function attach(
419 // All unmounts are batched in a single message.
420 // [TREE_OPERATION_REMOVE, removedIDLength, ...ids]
421 (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +
409 - // Mount/update/reorder operations
410 - pendingOperations.length
422 + // Mount operations
423 + pendingOperations.length +
424 + // Reorder operation come last because
425 + // bridge expects them to not change children length.
426 + // So both mounts and unmounts need to have happened by now.
427 + reorderOperations.length
428 );
429
430 // Identify which renderer this update is coming from.
@@ -444,6 +461,9 @@ export function attach(
461
462 // Fill in the rest of the operations.
463 operations.set(pendingOperations, i);
464 + i += pendingOperations.length;
465 +
466 + operations.set(reorderOperations, i);
467
468 if (__DEBUG__) {
469 printOperationsArray(operations);
@@ -454,12 +474,60 @@ export function attach(
474
475 pendingOperations.length = 0;
476 pendingMountIDs.clear();
477 + pendingReorderIDs.clear();
478 pendingUnmountIDs.clear();
479 pendingUnmountedRootID = null;
480 pendingStringTable.clear();
481 pendingStringTableLength = 0;
482 }
483
484 + function computePendingReorderOperations(): Array<number> {
485 + const ops = [];
486 + pendingReorderIDs.forEach(id => {
487 + const internalInstance = idToInternalInstanceMap.get(id);
488 + if (internalInstance === undefined) {
489 + return;
490 + }
491 + const prevChildIDs = internalInstanceToLastKnownChildrenMap.get(
492 + internalInstance
493 + );
494 + const nextChildIDs = getChildIDs(internalInstance);
495 + internalInstanceToLastKnownChildrenMap.set(
496 + internalInstance,
497 + nextChildIDs
498 + );
499 +
500 + let shouldResetChildren = false;
501 + if (prevChildIDs === undefined) {
502 + // We haven't computed children before.
503 + // So we'll have to do it now.
504 + // Next time they'll be cached for comparison
505 + // TODO: this might not make sense. Revisit.
506 + shouldResetChildren = true;
507 + } else if (nextChildIDs.length > 1) {
508 + if (prevChildIDs.length !== nextChildIDs.length) {
509 + shouldResetChildren = true;
510 + } else {
511 + for (let i = 0; i < prevChildIDs.length; i++) {
512 + if (prevChildIDs[i] !== nextChildIDs[i]) {
513 + shouldResetChildren = true;
514 + break;
515 + }
516 + }
517 + }
518 + }
519 + if (shouldResetChildren) {
520 + ops.push(TREE_OPERATION_REORDER_CHILDREN);
521 + ops.push(getID(internalInstance));
522 + ops.push(nextChildIDs.length);
523 + for (let i = 0; i < nextChildIDs.length; i++) {
524 + ops.push(nextChildIDs[i]);
525 + }
526 + }
527 + });
528 + return ops;
529 + }
530 +
531 function getStringID(str: string | null): number {
532 if (str === null) {
533 return 0;
@@ -652,6 +720,17 @@ export function attach(
720 queueFlushPendingEvents();
721 }
722
723 + function recordPendingReorder(internalInstance: InternalInstance) {
724 + const id = getID(internalInstance);
725 + pendingReorderIDs.add(id);
726 +
727 + if (__DEBUG__) {
728 + console.log('%crecordPendingReorder()', 'color: green', id);
729 + }
730 +
731 + queueFlushPendingEvents();
732 + }
733 +
734 function recordPendingUnmount(internalInstance: InternalInstance) {
735 const id = getID(internalInstance);
736