diff --git a/src/component.js b/src/component.js index aefaebe983..b7f2c2f86e 100644 --- a/src/component.js +++ b/src/component.js @@ -44,6 +44,23 @@ extend(Component.prototype, { // return true; // }, + /** Sets a `requestIdleCallback` id on the component. If there are + * repeated calls to render the component (and the component is set to render + * asynchronously), the existing rIC callback will be cancelled using this id. + * + * @private + */ + set renderId (id) { + this._id = id; + }, + + /** Gets the stored `requestIdleCallback` id. + * + * @private + */ + get renderId () { + return this._id || null; + }, /** Returns a function that sets a state property when called. * Calling linkState() repeatedly with the same arguments returns a cached link function. diff --git a/src/vdom/component.js b/src/vdom/component.js index 852b5a2884..932f9435d2 100644 --- a/src/vdom/component.js +++ b/src/vdom/component.js @@ -17,39 +17,67 @@ import { removeNode } from '../dom/index'; * @param {boolean} [opts.render=true] If `false`, no render will be triggered. */ export function setComponentProps(component, props, opts, context, mountAll) { - if (component._disable) return; - component._disable = true; - if ((component.__ref = props.ref)) delete props.ref; - if ((component.__key = props.key)) delete props.key; + const requestIdleCallback = + typeof window !== 'undefined' && + typeof window.requestIdleCallback !== 'undefined' ? + window.requestIdleCallback : + null; - if (!component.base || mountAll) { - if (component.componentWillMount) component.componentWillMount(); - } - else if (component.componentWillReceiveProps) { - component.componentWillReceiveProps(props, context); - } + const cancelIdleCallback = + requestIdleCallback !== 'null' ? + window.cancelIdleCallback : + null; - if (context && context!==component.context) { - if (!component.prevContext) component.prevContext = component.context; - component.context = context; + let handler = cb => cb(); + + // Only support async if rIC is supported and the component requests it. + if (requestIdleCallback && component.renderAsync) { + handler = requestIdleCallback; + + // Remove any pending requests for this component. + if (component.renderId) { + cancelIdleCallback(component.renderId); + } } - if (!component.prevProps) component.prevProps = component.props; - component.props = props; + component.renderId = handler(() => { + component.renderId = null; + + if (component._disable) return; + component._disable = true; - component._disable = false; + if ((component.__ref = props.ref)) delete props.ref; + if ((component.__key = props.key)) delete props.key; - if (opts!==NO_RENDER) { - if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { - renderComponent(component, SYNC_RENDER, mountAll); + if (!component.base || mountAll) { + if (component.componentWillMount) component.componentWillMount(); } - else { - enqueueRender(component); + else if (component.componentWillReceiveProps) { + component.componentWillReceiveProps(props, context); + } + + if (context && context!==component.context) { + if (!component.prevContext) component.prevContext = component.context; + component.context = context; + } + + if (!component.prevProps) component.prevProps = component.props; + component.props = props; + + component._disable = false; + + if (opts!==NO_RENDER) { + if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { + renderComponent(component, SYNC_RENDER, mountAll); + } + else { + enqueueRender(component); + } } - } - if (component.__ref) component.__ref(component); + if (component.__ref) component.__ref(component); + }); }