Skip to content

Commit ab847cb

Browse files
authored
Track the parent block to optimize hierarchy selectors (#16392)
1 parent 21e2dce commit ab847cb

File tree

4 files changed

+607
-111
lines changed

4 files changed

+607
-111
lines changed

packages/block-editor/src/store/reducer.js

+104-20
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ function mapBlockOrder( blocks, rootClientId = '' ) {
5454
return result;
5555
}
5656

57+
/**
58+
* Given an array of blocks, returns an object where each key contains
59+
* the clientId of the block and the value is the parent of the block.
60+
*
61+
* @param {Array} blocks Blocks to map.
62+
* @param {?string} rootClientId Assumed root client ID.
63+
*
64+
* @return {Object} Block order map object.
65+
*/
66+
function mapBlockParents( blocks, rootClientId = '' ) {
67+
return blocks.reduce( ( result, block ) => Object.assign(
68+
result,
69+
{ [ block.clientId ]: rootClientId },
70+
mapBlockParents( block.innerBlocks, block.clientId )
71+
), {} );
72+
}
73+
5774
/**
5875
* Helper method to iterate through all blocks, recursing into inner blocks,
5976
* applying a transformation function to each one.
@@ -264,16 +281,40 @@ function withIgnoredBlockChange( reducer ) {
264281
* @return {Function} Enhanced reducer function.
265282
*/
266283
const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => {
267-
if ( state && action.type === 'REMOVE_BLOCKS' ) {
268-
const clientIds = [ ...action.clientIds ];
284+
const getAllChildren = ( clientIds ) => {
285+
let result = clientIds;
286+
for ( let i = 0; i < result.length; i++ ) {
287+
if ( ! state.order[ result[ i ] ] ) {
288+
continue;
289+
}
269290

270-
// For each removed client ID, include its inner blocks to remove,
271-
// recursing into those so long as inner blocks exist.
272-
for ( let i = 0; i < clientIds.length; i++ ) {
273-
clientIds.push( ...state.order[ clientIds[ i ] ] );
291+
if ( result === clientIds ) {
292+
result = [ ...result ];
293+
}
294+
295+
result.push( ...state.order[ result[ i ] ] );
274296
}
275297

276-
action = { ...action, clientIds };
298+
return result;
299+
};
300+
301+
if ( state ) {
302+
switch ( action.type ) {
303+
case 'REMOVE_BLOCKS':
304+
action = {
305+
...action,
306+
type: 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN',
307+
removedClientIds: getAllChildren( action.clientIds ),
308+
};
309+
break;
310+
case 'REPLACE_BLOCKS':
311+
action = {
312+
...action,
313+
type: 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN',
314+
replacedClientIds: getAllChildren( action.clientIds ),
315+
};
316+
break;
317+
}
277318
}
278319

279320
return reducer( state, action );
@@ -306,6 +347,10 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
306347
...omit( state.order, visibleClientIds ),
307348
...mapBlockOrder( action.blocks ),
308349
},
350+
parents: {
351+
...omit( state.parents, visibleClientIds ),
352+
...mapBlockParents( action.blocks ),
353+
},
309354
};
310355
}
311356

@@ -435,18 +480,18 @@ export const blocks = flow(
435480
...getFlattenedBlocksWithoutAttributes( action.blocks ),
436481
};
437482

438-
case 'REPLACE_BLOCKS':
483+
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
439484
if ( ! action.blocks ) {
440485
return state;
441486
}
442487

443488
return {
444-
...omit( state, action.clientIds ),
489+
...omit( state, action.replacedClientIds ),
445490
...getFlattenedBlocksWithoutAttributes( action.blocks ),
446491
};
447492

448-
case 'REMOVE_BLOCKS':
449-
return omit( state, action.clientIds );
493+
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
494+
return omit( state, action.removedClientIds );
450495
}
451496

452497
return state;
@@ -511,18 +556,18 @@ export const blocks = flow(
511556
...getFlattenedBlockAttributes( action.blocks ),
512557
};
513558

514-
case 'REPLACE_BLOCKS':
559+
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
515560
if ( ! action.blocks ) {
516561
return state;
517562
}
518563

519564
return {
520-
...omit( state, action.clientIds ),
565+
...omit( state, action.replacedClientIds ),
521566
...getFlattenedBlockAttributes( action.blocks ),
522567
};
523568

524-
case 'REMOVE_BLOCKS':
525-
return omit( state, action.clientIds );
569+
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
570+
return omit( state, action.removedClientIds );
526571
}
527572

528573
return state;
@@ -609,7 +654,7 @@ export const blocks = flow(
609654
};
610655
}
611656

612-
case 'REPLACE_BLOCKS': {
657+
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': {
613658
const { clientIds } = action;
614659
if ( ! action.blocks ) {
615660
return state;
@@ -618,7 +663,7 @@ export const blocks = flow(
618663
const mappedBlocks = mapBlockOrder( action.blocks );
619664

620665
return flow( [
621-
( nextState ) => omit( nextState, clientIds ),
666+
( nextState ) => omit( nextState, action.replacedClientIds ),
622667
( nextState ) => ( {
623668
...nextState,
624669
...omit( mappedBlocks, '' ),
@@ -642,20 +687,59 @@ export const blocks = flow(
642687
] )( state );
643688
}
644689

645-
case 'REMOVE_BLOCKS':
690+
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
646691
return flow( [
647692
// Remove inner block ordering for removed blocks
648-
( nextState ) => omit( nextState, action.clientIds ),
693+
( nextState ) => omit( nextState, action.removedClientIds ),
649694

650695
// Remove deleted blocks from other blocks' orderings
651696
( nextState ) => mapValues( nextState, ( subState ) => (
652-
without( subState, ...action.clientIds )
697+
without( subState, ...action.removedClientIds )
653698
) ),
654699
] )( state );
655700
}
656701

657702
return state;
658703
},
704+
705+
// While technically redundant data as the inverse of `order`, it serves as
706+
// an optimization for the selectors which derive the ancestry of a block.
707+
parents( state = {}, action ) {
708+
switch ( action.type ) {
709+
case 'RESET_BLOCKS':
710+
return mapBlockParents( action.blocks );
711+
712+
case 'RECEIVE_BLOCKS':
713+
return {
714+
...state,
715+
...mapBlockParents( action.blocks ),
716+
};
717+
718+
case 'INSERT_BLOCKS':
719+
return {
720+
...state,
721+
...mapBlockParents( action.blocks, action.rootClientId || '' ),
722+
};
723+
724+
case 'MOVE_BLOCK_TO_POSITION': {
725+
return {
726+
...state,
727+
[ action.clientId ]: action.toRootClientId || '',
728+
};
729+
}
730+
731+
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
732+
return {
733+
...omit( state, action.replacedClientIds ),
734+
...mapBlockParents( action.blocks, state[ action.clientIds[ 0 ] ] ),
735+
};
736+
737+
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
738+
return omit( state, action.removedClientIds );
739+
}
740+
741+
return state;
742+
},
659743
} );
660744

661745
/**

packages/block-editor/src/store/selectors.js

+14-31
Original file line numberDiff line numberDiff line change
@@ -458,22 +458,11 @@ export function getSelectedBlock( state ) {
458458
*
459459
* @return {?string} Root client ID, if exists
460460
*/
461-
export const getBlockRootClientId = createSelector(
462-
( state, clientId ) => {
463-
const { order } = state.blocks;
464-
465-
for ( const rootClientId in order ) {
466-
if ( includes( order[ rootClientId ], clientId ) ) {
467-
return rootClientId;
468-
}
469-
}
470-
471-
return null;
472-
},
473-
( state ) => [
474-
state.blocks.order,
475-
]
476-
);
461+
export function getBlockRootClientId( state, clientId ) {
462+
return state.blocks.parents[ clientId ] !== undefined ?
463+
state.blocks.parents[ clientId ] :
464+
null;
465+
}
477466

478467
/**
479468
* Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks.
@@ -483,21 +472,15 @@ export const getBlockRootClientId = createSelector(
483472
*
484473
* @return {string} Root client ID
485474
*/
486-
export const getBlockHierarchyRootClientId = createSelector(
487-
( state, clientId ) => {
488-
let rootClientId = clientId;
489-
let current = clientId;
490-
while ( rootClientId ) {
491-
current = rootClientId;
492-
rootClientId = getBlockRootClientId( state, current );
493-
}
494-
495-
return current;
496-
},
497-
( state ) => [
498-
state.blocks.order,
499-
]
500-
);
475+
export function getBlockHierarchyRootClientId( state, clientId ) {
476+
let current = clientId;
477+
let parent;
478+
do {
479+
parent = current;
480+
current = state.blocks.parents[ current ];
481+
} while ( current );
482+
return parent;
483+
}
501484

502485
/**
503486
* Returns the client ID of the block adjacent one at the given reference

0 commit comments

Comments
 (0)