-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Fix editing performance in Widgets Customizer #30654
Conversation
Size Change: +39.9 kB (+3%) Total Size: 1.46 MB
ℹ️ View Unchanged
|
canUndo() { | ||
return this.historyIndex > 0; | ||
} | ||
canRedo() { | ||
return this.historyIndex < this.history.length - 1; | ||
} | ||
_seek( historyIndex ) { | ||
const currentWidgets = this.getWidgets(); | ||
|
||
this.historyIndex = historyIndex; | ||
|
||
const widgets = this.history[ this.historyIndex ]; | ||
|
||
this._internalUpdateWidgets( widgets ); | ||
|
||
this._emit( currentWidgets, this.getWidgets() ); | ||
} | ||
undo() { | ||
if ( ! this.canUndo() ) { | ||
return; | ||
} | ||
|
||
this._seek( this.historyIndex - 1 ); | ||
} | ||
redo() { | ||
if ( ! this.canRedo() ) { | ||
return; | ||
} | ||
|
||
this._seek( this.historyIndex + 1 ); |
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.
These are currently unused, but paving the way for the undo/redo feature in the future.
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 fantastic! I tested this locally and it's a huge huge huge improvement. useSidebarBlockEditor
is a lot easier to understand now, too. I left a few minor comments.
packages/customize-widgets/src/components/sidebar-block-editor/sidebar-editor-provider.js
Outdated
Show resolved
Hide resolved
ignoreIncoming.current = true; | ||
( nextBlocks ) => { | ||
setBlocks( ( prevBlocks ) => { | ||
if ( isShallowEqual( prevBlocks, nextBlocks ) ) { |
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.
Is this ever true? I don't understand why useBlockSync
would call onChange
if nothing has changed.
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.
It happened once in my testing, not sure why though 🤔.
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.
IIRC, useBlockSync
works based on the reference to the blocks array; I don't know if it goes through the array of blocks. It theoretically works because the block-editor
store is supposed to make sure that references to a slice of blocks don't change unless the blocks actually did change. (To prevent re-renders and stuff.)
It might be worth changing that line to use isShawllowEqual
as well. I think it may have been avoided to prevent looping over the entire array of blocks every time the subscription callback fires
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.
It might be worth changing that line to use
isShawllowEqual
as well. I think it may have been avoided to prevent looping over the entire array of blocks every time the subscription callback fires
Yeah I think that would make sense! I believe it also means that I don't have to do the weird if (nextBlocks === newBlocks) newBlocks = [...nextBlocks]
thing below.
Want to make an issue/PR?
const prevWidget = sidebar.getWidget( widgetId ); | ||
|
||
// Bail out updates by returning the previous widgets. | ||
// Deep equality is necessary until the block editor's internals changes. |
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 do you mean by "until the block editor's internals change"? Is this ever going to happen?
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.
It seems like we currently use isEqual
under the hood in our reducer for every edit in the block editor package. Until we optimize it there, it's best to follow the same logic here and do a isEqual
check for every edit as well. I have no idea if we ever have time to optimize it though 😅.
packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js
Outdated
Show resolved
Hide resolved
this.canUndo = this.canUndo.bind( this ); | ||
this.canRedo = this.canRedo.bind( this ); | ||
this.undo = this.undo.bind( this ); | ||
this.redo = this.redo.bind( this ); |
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.
Binding these shouldn't be necessary. It's only needed when we use a class member function as a callback.
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.
It's just easier if any components/hooks want to access these methods without having to memoize/bind them again. We don't need these now though.
const { canUndo, canRedo, undo, redo } = sidebar;
packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js
Show resolved
Hide resolved
_handleSettingChange( newWidgetIds, oldWidgetIds ) { | ||
const addedWidgetIds = difference( newWidgetIds, oldWidgetIds ); | ||
const removedWidgetIds = difference( oldWidgetIds, newWidgetIds ); | ||
_getWidgetIds() { |
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.
It's slightly confusing that getWidgets()
and _getWidgetIds()
are named similarly but the former returns "adapted" data and the returns data from the underlying setting. I'd consider not having _getWidgetIds()
and instead using this.setting.get()
everywhere. It's more verbose but, in my opinion, less ambiguous.
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 tried that but I thought this.setting.get()
isn't very clear what it would return.
packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js
Outdated
Show resolved
Hide resolved
packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js
Show resolved
Hide resolved
packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js
Outdated
Show resolved
Hide resolved
2c0b10d
to
b8a326e
Compare
Shoot, I kept forgetting to change my base before rebasing and force pushing, sorry for the pings folks 😅. |
Description
This fixes an important performance issue in the Widgets Customizer where editing any block will re-render the whole block editor. This branch is based on #30221 for easier review but should be re-target to trunk before merge.
The problem with the original approach is that every time we update any block we create a new instance of the
nextBlocks
array and set it back to the component's state viasetState
. This feels intuitive but apparently it breaks the optimizationuseBlockSync
has. Turns out we need to make sure thenextBlocks
instance we pass to the<BlockEditorProvider>
'svalue
should be referential equal to thenextBlocks
we received from theonChange
callback. Or else all changes are marked as persistent and will re-render the whole block editor.The solution is simply to call
setState
with thenextBlocks
we receive from the callback whenever possible.I also essentially refactored the code in
SidebarAdaptor
anduseSidebarBlockEditor
to use the customize API as the source of truth of all states, and simplify the logic a little bit. I added a few methods regarding undo/redo in #30400. They're currently unused yet though, but they should work. We can add the UI in another PR.How has this been tested?
Screenshots
Kapture.2021-04-09.at.14.49.28.mp4
Types of changes
Bug fix
Checklist:
*.native.js
files for terms that need renaming or removal).