From 318da4891344b8f1974eec8002f87104f66170b4 Mon Sep 17 00:00:00 2001 From: Jannick Garthen Date: Sat, 22 Sep 2018 18:08:30 +0200 Subject: [PATCH] feat(ObserveViewport): allow to perform layouts reads in one badge for all rendered components before updating --- README.md | 15 +++++++++++++++ lib/ObserveViewport.tsx | 9 ++++++--- lib/ViewportProvider.tsx | 22 ++++++++++++++++------ lib/types.ts | 4 +++- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0b2ea69..9cb3093 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,21 @@ render( ); ``` +When an update is triggered, sometimes further calculations on the DOM which might trigger [layouts/ reflows](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) are required to execute a task. +In general the best performance is archive by first reading all the values in one badge and later update the DOM again. With multiple components in one page this can become difficult. + +The optional `recalculateLayoutBeforeUpdate` property, which accepts a function, will allow to exactly handle those reads in one badge for all components to later perform the update: + +* first all `recalculateLayoutBeforeUpdate` functions for all components are executed. +* second all `onUpdate` function are called which receive the value returned from `recalculateLayoutBeforeUpdate` as the second argument. + +``` javascript + el.getBoundingClientRect()} + onUpdate={({ scroll }, rect) => console.log('Top offset: ', scroll.y + rect.top))} +/> +``` + #### Omit events In case only certain updates are required `connectViewport` allows an `omit` option to skip updates to `scroll` and `dimensions` events. If both strings are included within the `omit` array, no events will get triggered at all. diff --git a/lib/ObserveViewport.tsx b/lib/ObserveViewport.tsx index 2b1a08b..d8fb8f8 100644 --- a/lib/ObserveViewport.tsx +++ b/lib/ObserveViewport.tsx @@ -23,7 +23,8 @@ interface IState extends IChildProps {} interface IProps { children?: (props: IChildProps) => React.ReactNode; - onUpdate?: (props: IChildProps) => void; + onUpdate?: (props: IChildProps, layoutSnapshot: any) => void; + recalculateLayoutBeforeUpdate?: (props: IChildProps) => any; disableScrollUpdates: boolean; disableDimensionsUpdates: boolean; } @@ -84,7 +85,7 @@ export default class ObserveViewport extends React.Component { raf.cancel(this.tickId); } - handleViewportUpdate = (viewport: IViewport) => { + handleViewportUpdate = (viewport: IViewport, layoutSnapshot: any) => { const scroll = this.props.disableScrollUpdates ? null : viewport.scroll; const dimensions = this.props.disableDimensionsUpdates ? null @@ -95,7 +96,7 @@ export default class ObserveViewport extends React.Component { }; if (this.props.onUpdate) { - this.props.onUpdate(nextViewport); + this.props.onUpdate(nextViewport, layoutSnapshot); } this.tickId = raf(() => { @@ -119,12 +120,14 @@ export default class ObserveViewport extends React.Component { this.removeViewportChangeListener(this.handleViewportUpdate, { notifyScroll: !this.props.disableScrollUpdates, notifyDimensions: !this.props.disableDimensionsUpdates, + recalculateLayoutBeforeUpdate: this.props.recalculateLayoutBeforeUpdate, }); } this.removeViewportChangeListener = removeViewportChangeListener; addViewportChangeListener(this.handleViewportUpdate, { notifyScroll: !this.props.disableScrollUpdates, notifyDimensions: !this.props.disableDimensionsUpdates, + recalculateLayoutBeforeUpdate: this.props.recalculateLayoutBeforeUpdate, }); return null; }; diff --git a/lib/ViewportProvider.tsx b/lib/ViewportProvider.tsx index 185a8b6..e27f1cf 100644 --- a/lib/ViewportProvider.tsx +++ b/lib/ViewportProvider.tsx @@ -238,13 +238,23 @@ export default class ViewportProvider extends React.PureComponent { if (scrollDidUpdate || dimensionsDidUpdate) { const publicState = this.getPropsFromState(); - this.listeners.forEach(({ handler, notifyScroll, notifyDimensions }) => { - if ( + const updatableListeners = this.listeners.filter( + ({ notifyScroll, notifyDimensions }) => (notifyScroll && scrollDidUpdate) || - (notifyDimensions && dimensionsDidUpdate) - ) { - handler(publicState); - } + (notifyDimensions && dimensionsDidUpdate), + ); + const layouts = updatableListeners.map( + ({ recalculateLayoutBeforeUpdate }) => { + if (recalculateLayoutBeforeUpdate) { + return recalculateLayoutBeforeUpdate(publicState); + } + return null; + }, + ); + + updatableListeners.forEach(({ handler }, index) => { + const layout = layouts[index]; + handler(publicState, layout); }); } }; diff --git a/lib/types.ts b/lib/types.ts index c2849a6..6b9f536 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -49,10 +49,12 @@ export interface IViewport { } export type TViewportChangeHandler = ( - { scroll, dimensions }: IViewport, + viewport: IViewport, + layoutSnapshot: any, ) => void; export interface IViewportChangeOptions { notifyScroll: boolean; notifyDimensions: boolean; + recalculateLayoutBeforeUpdate?: (viewport: IViewport) => any; }