-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance: optimize the usage of getBlockIndex #13067
Conversation
@@ -769,6 +773,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { | |||
} ); | |||
|
|||
export default compose( | |||
pure, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is useful to avoid rerendering all the blocks when we add/remove a block in the block list (parent component).
I was also tempted to replace it with React.memo
as it may be more optimized than our HoC. It would be good to compare performance of these two.
@@ -189,7 +189,6 @@ class BlockList extends Component { | |||
{ map( blockClientIds, ( clientId, blockIndex ) => ( | |||
<BlockListBlock | |||
key={ 'block-' + clientId } | |||
index={ blockIndex } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent 2 hours trying to find why the blocks were rerendering, it turns out we had a useless prop (that was not even used :P)
isLocked: !! getTemplateLock( rootClientId ), | ||
blocks, | ||
canDuplicate, | ||
rootClientId, | ||
extraProps: props, | ||
}; | ||
} ), | ||
withDispatch( ( dispatch, props ) => { | ||
withDispatch( ( dispatch, props, { select } ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was missed because I was explicitly looking for
withDispatch( ( dispatch, ownProps )
orwithDispatch( ( dispatch, {
. We apparently have more variation 😬
This is the only occurrence of the second argument being named as "props" for withDispatch
, and best I could tell (by a regular expression search of withDispatch\( \( dispatch, [^{o]
) there are no other variations.
|
||
return { | ||
title: getEditedPostAttribute( 'title' ), | ||
hasItems: hasInserterItems( rootClientId ), | ||
rootClientId, | ||
index, | ||
rootClientId: rootClientId || getBlockInsertionPoint().rootClientId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have been changed from rootClientId,
? There's redundancy here in that the same fallback behavior occurs a few lines above.
const { | ||
__experimentalFetchReusableBlocks: fetchReusableBlocks, | ||
showInsertionPoint, | ||
hideInsertionPoint, | ||
} = dispatch( 'core/editor' ); | ||
|
||
// eslint-disable-next-line no-restricted-syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a "Disable reason" if only to clarify what's actually being disabled here, given that no-restricted-syntax
is an umbrella rule containing many miscellaneous custom rules.
860fec0
to
94d8884
Compare
Rebased this, can I get some reviews? |
Updated to fix the tests and this actually also fixes #13349 :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a quite complex PR. I reviewed it but I was not able properly validate changes in packages/editor/src/components/inserter/menu.js file. I wIll try again after I play with PR in the browser.
Everything else seems to be correct. It's hard to follow as there are some changes in the props passed.
return { | ||
isLocked: !! getTemplateLock( rootClientId ), | ||
getClientIdsOfDescendants, | ||
getBlockIndex, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that it isn't something we can handle using withDispatch
and select
call.
@@ -101,6 +102,7 @@ class BlockDropZone extends Component { | |||
return; | |||
} | |||
|
|||
const dstIndex = dstClientId ? getBlockIndex( dstClientId, dstRootClientId ) : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What dst
stands for? It would read better if we would avoid such abbreviations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Nice improvements 👍
This was difficult to review because it wasn't easy to associate individual changes with their goal, as there appears to be about a dozen separate goals of this pull request.
getBlockSelectionEnd, | ||
getBlockOrder, | ||
} = select( 'core/editor' ); | ||
const { clientId, rootClientId, isAppender } = ownProps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand the purpose of this new isAppender
prop. Isn't it enough to assume that if the inserter is not provided a clientId
and there's no current selection, it should take the appending behavior to the rootClientId
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, looking more, I think I understand it a bit better, particularly when the last block is not a paragraph and you have a selection, it's important that the inserter which is shown at the end of content would insert by appending, not after whatever selection happens to exist. The alternative would be that the default appender at the end of content provides the clientId
of the last block, but I think the expressiveness of this new prop is preferable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also sometimes, there's no block inside a container block with an appender, and the currently selected block is elsewhere on the page, which means the regular flow would insert the new block under the selected block and not inside the container block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically speaking, I'd love to remove the behavior we have where we insert a block after the selected block, it could be considered confusing and there's the in-between inserters to solve the same use-case.
I won't do it in this PR but would love thoughts cc @jasmussen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically speaking, I'd love to remove the behavior we have where we insert a block after the selected block, it could be considered confusing and there's the in-between inserters to solve the same use-case.
Followup question to this: if you used the Add Block button in the top toolbar on a very long page, where would the block be inserted? If the answer is "at the end", it doesn't seem like a good solution. Performance is important, of course, but is there any other way we can do it?
Most word processors insert non-text content where the caret is at. Honestly with the sibling inserter is a new solution with limited prior art. I understand it's a different beast, but we should still not trivially remove this. @alexislloyd what are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was asking for thoughts and I have them, thanks @jasmussen :)
My impression was that sometimes, the behavior was not well-understood/confusing for some people but I totally agree that the technically matters shouldn't dictate the decision here, as it's not a big drawback to support this.
} | ||
|
||
// If there a selected block, we insert after the selected block. | ||
const end = getBlockSelectionEnd(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getBlockSelectionEnd
is performant enough, but there are optimization opportunities here to avoid calling it when isAppender
is true (allow the short-circuiting of the following condition's ! isAppender
).
let end;
if ( ! isAppender && ( end = getBlockSelectionEnd() ) ) {
const selectedBlockRootClientId = getBlockRootClientId( end ) || undefined;
return {
index: getBlockIndex( end, selectedBlockRootClientId ) + 1,
rootClientId: selectedBlockRootClientId,
};
}
Or:
if ( ! isAppender ) {
const end = getBlockSelectionEnd();
if ( end ) {
const selectedBlockRootClientId = getBlockRootClientId( end ) || undefined;
return {
index: getBlockIndex( end, selectedBlockRootClientId ) + 1,
rootClientId: selectedBlockRootClientId,
};
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I forgot this is only called on specific user interactions, not in a withSelect
, so not nearly as big a deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my testing, everything works great in comparison to master
. It's even better because of this bug fixed.
I spent time trying to figure out why there is no appender after the last block is a Paragraph with content, but it seems like a design decision. @jasmussen is it explained somewhere?
…rnmobile/372-add-enter-key-detection-to-title * 'master' of https://github.com/WordPress/gutenberg: Add new RSS Block (#7966) Performance: optimize the usage of getBlockIndex (#13067)
@gziolo perhaps this? #3078 (comment) |
It's slightly older I think but thanks for checking :) |
refs #11782 closes #13349
When adding blocks or pressing "Enter", the index of the blocks following the insertion point changes which means all the blocks following this block rerender because they have the
order
as a prop.In this PR, I move the
getBlockIndex
selector usage to the event handlers instead of computing it inwithSelect
and passing it as a prop to the component to avoid heavy rerenders when pressing "Enter".