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
Rewrite afterLayoutFlush
as a generic TS function.
#32926
Changes from all commits
9e42df4
fb5e6a0
1ef3c38
b660050
166a47d
884ae27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,11 @@ | ||
/** @format */ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { TimerHandle } from 'types'; | ||
|
||
interface Cancelable { | ||
cancel: () => void; | ||
} | ||
|
||
/** | ||
* Creates a delayed function that invokes `func` as soon as possible after the next layout | ||
|
@@ -11,19 +18,20 @@ | |
* Inspired by the Firefox performance best practices MDN article at: | ||
* https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers | ||
* | ||
* @param {Function} func The function to be invoked after the layout flush | ||
* @returns {Function} Returns the new delayed function | ||
* @param func - The function to be invoked after the layout flush | ||
* @returns The new delayed function | ||
*/ | ||
export default function afterLayoutFlush( func ) { | ||
let rafHandle = undefined; | ||
let timeoutHandle = undefined; | ||
export default function afterLayoutFlush< T extends ( ...args: any[] ) => any >( func: T ) { | ||
let timeoutHandle: TimerHandle | undefined = undefined; | ||
let rafHandle: number | undefined = undefined; | ||
|
||
const hasRAF = typeof requestAnimationFrame === 'function'; | ||
|
||
let lastThis, lastArgs; | ||
let lastThis: any; | ||
let lastArgs: any[] | undefined; | ||
|
||
const scheduleRAF = function( rafFunc ) { | ||
return function( ...args ) { | ||
const scheduleRAF = function( rafFunc: T ) { | ||
return function( this: any, ...args: any[] ) { | ||
lastThis = this; | ||
lastArgs = args; | ||
|
||
|
@@ -37,11 +45,11 @@ export default function afterLayoutFlush( func ) { | |
rafHandle = undefined; | ||
rafFunc(); | ||
} ); | ||
}; | ||
} as T; | ||
}; | ||
|
||
const scheduleTimeout = function( timeoutFunc ) { | ||
return function( ...args ) { | ||
const scheduleTimeout = function( timeoutFunc: T ) { | ||
return function( this: any, ...args: any[] ) { | ||
if ( ! hasRAF ) { | ||
lastThis = this; | ||
lastArgs = args; | ||
|
@@ -55,7 +63,7 @@ export default function afterLayoutFlush( func ) { | |
} | ||
|
||
timeoutHandle = setTimeout( () => { | ||
const callArgs = lastArgs; | ||
const callArgs = lastArgs !== undefined ? lastArgs : []; | ||
const callThis = lastThis; | ||
|
||
lastArgs = undefined; | ||
|
@@ -65,17 +73,18 @@ export default function afterLayoutFlush( func ) { | |
timeoutHandle = undefined; | ||
timeoutFunc.apply( callThis, callArgs ); | ||
}, 0 ); | ||
}; | ||
} as T; | ||
}; | ||
|
||
// if RAF is not supported (in Node.js test environment), the wrapped function | ||
// will only set a timeout. | ||
let wrappedFunc = scheduleTimeout( func ); | ||
let wrappedFunc: T = scheduleTimeout( func ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we always create the timeout function even when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The correct flow is to go through both. The article linked to in the file explains why, exactly, but it basically boils down to browser rendering timing quirks. When |
||
if ( hasRAF ) { | ||
wrappedFunc = scheduleRAF( wrappedFunc ); | ||
} | ||
|
||
wrappedFunc.cancel = () => { | ||
const wrappedWithCancel = wrappedFunc as T & Cancelable; | ||
wrappedWithCancel.cancel = () => { | ||
lastThis = undefined; | ||
lastArgs = undefined; | ||
|
||
|
@@ -90,5 +99,5 @@ export default function afterLayoutFlush( func ) { | |
} | ||
}; | ||
|
||
return wrappedFunc; | ||
return wrappedWithCancel; | ||
} |
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 may be the equivalent of
<T extends Function>
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 thought so too, but switching to that produces the following error further down in the
scheduleRAF
andscheduleTimeout
return
s: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.
sorry for the delay - it's something we'd have to coordinate below.
also, can you help me understand the purpose of
lastThis
and the like? what are those there fore and why are we using them? are we having trouble withthis
binding and maintaining context?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.
afterLayoutFlush
is meant to be as bullet-proof as possible with regards tothis
usage, similar todefer
and friends in Lodash. Because of that, we store and restore thethis
we receive, so that whatever the context the function is used in, everything will still work.