From 808790505e4504238d9bd723c3b6f2d8665a39bb Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Sat, 18 Mar 2017 11:56:52 -0700 Subject: [PATCH] [change] onLayout improvements 1. Fires when window resizes 2. Guards against nodes being unmounted Fix #397 Ref #60 --- package.json | 1 - src/apis/Dimensions/index.js | 9 ++-- src/apis/StyleSheet/StyleManager.js | 4 +- src/apis/UIManager/index.js | 29 +++++++----- src/modules/applyLayout/index.js | 73 +++++++++++++++++++++++------ yarn.lock | 2 +- 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 41a256f6..0c8bc4f0 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "dependencies": { "animated": "^0.2.0", "array-find-index": "^1.0.2", - "asap": "^2.0.5", "babel-runtime": "^6.23.0", "debounce": "^1.0.0", "deep-assign": "^2.0.0", diff --git a/src/apis/Dimensions/index.js b/src/apis/Dimensions/index.js index 9cb93363..7fb7907a 100644 --- a/src/apis/Dimensions/index.js +++ b/src/apis/Dimensions/index.js @@ -6,11 +6,11 @@ * @flow */ +import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import debounce from 'debounce'; -import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; import invariant from 'fbjs/lib/invariant'; -const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }; +const win = canUseDOM ? window : { screen: {} }; const dimensions = {}; @@ -38,6 +38,9 @@ class Dimensions { } Dimensions.set(); -ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50)); + +if (canUseDOM) { + window.addEventListener('resize', debounce(Dimensions.set, 16), false); +} module.exports = Dimensions; diff --git a/src/apis/StyleSheet/StyleManager.js b/src/apis/StyleSheet/StyleManager.js index 08d194de..310844b5 100644 --- a/src/apis/StyleSheet/StyleManager.js +++ b/src/apis/StyleSheet/StyleManager.js @@ -1,7 +1,7 @@ -import asap from 'asap'; import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import generateCss from './generateCss'; import hash from './hash'; +import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame'; import staticCss from './staticCss'; const emptyObject = {}; @@ -111,7 +111,7 @@ class StyleManager { className = createClassName(prop, value); this._addToCache(className, prop, value); if (canUseDOM) { - asap(() => { + requestAnimationFrame(() => { const sheet = this.mainSheet.sheet; // avoid injecting if the rule already exists (e.g., server rendered, hot reload) if (this.mainSheet.textContent.indexOf(className) === -1) { diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js index 34b0ed20..f9ffd33b 100644 --- a/src/apis/UIManager/index.js +++ b/src/apis/UIManager/index.js @@ -1,5 +1,5 @@ -import asap from 'asap'; import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations'; +import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame'; const getRect = node => { const height = node.offsetHeight; @@ -15,13 +15,15 @@ const getRect = node => { }; const measureLayout = (node, relativeToNativeNode, callback) => { - asap(() => { - const relativeNode = relativeToNativeNode || node.parentNode; - const relativeRect = getRect(relativeNode); - const { height, left, top, width } = getRect(node); - const x = left - relativeRect.left; - const y = top - relativeRect.top; - callback(x, y, width, height, left, top); + requestAnimationFrame(() => { + const relativeNode = relativeToNativeNode || (node && node.parentNode); + if (node && relativeNode) { + const relativeRect = getRect(relativeNode); + const { height, left, top, width } = getRect(node); + const x = left - relativeRect.left; + const y = top - relativeRect.top; + callback(x, y, width, height, left, top); + } }); }; @@ -43,13 +45,16 @@ const UIManager = { }, measureInWindow(node, callback) { - const { height, left, top, width } = getRect(node); - callback(left, top, width, height); + requestAnimationFrame(() => { + if (node) { + const { height, left, top, width } = getRect(node); + callback(left, top, width, height); + } + }); }, measureLayout(node, relativeToNativeNode, onFail, onSuccess) { - const relativeTo = relativeToNativeNode || node.parentNode; - measureLayout(node, relativeTo, onSuccess); + measureLayout(node, relativeToNativeNode, onSuccess); }, updateView(node, props, component /* only needed to surpress React errors in development */) { diff --git a/src/modules/applyLayout/index.js b/src/modules/applyLayout/index.js index 92e2b51f..4d9f4ce8 100644 --- a/src/modules/applyLayout/index.js +++ b/src/modules/applyLayout/index.js @@ -5,24 +5,66 @@ * @flow */ -import emptyFunction from 'fbjs/lib/emptyFunction'; +import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; +import debounce from 'debounce'; const emptyObject = {}; +const registry = {}; + +let id = 1; +const guid = () => `r-${id++}`; + +if (canUseDOM) { + const triggerAll = () => { + Object.keys(registry).forEach(key => { + const instance = registry[key]; + instance._handleLayout(); + }); + }; + + window.addEventListener('resize', debounce(triggerAll, 16), false); +} + +const safeOverride = (original, next) => { + if (original) { + return function prototypeOverride() { + original.call(this); + next.call(this); + }; + } + return next; +}; const applyLayout = Component => { - const componentDidMount = Component.prototype.componentDidMount || emptyFunction; - const componentDidUpdate = Component.prototype.componentDidUpdate || emptyFunction; + const componentDidMount = Component.prototype.componentDidMount; + const componentDidUpdate = Component.prototype.componentDidUpdate; + const componentWillUnmount = Component.prototype.componentWillUnmount; - Component.prototype.componentDidMount = function() { - componentDidMount.call(this); - this._layoutState = emptyObject; - this._handleLayout(); - }; + Component.prototype.componentDidMount = safeOverride( + componentDidMount, + function componentDidMount() { + this._layoutState = emptyObject; + this._isMounted = true; + this._onLayoutId = guid(); + registry[this._onLayoutId] = this; + this._handleLayout(); + } + ); - Component.prototype.componentDidUpdate = function() { - componentDidUpdate.call(this); - this._handleLayout(); - }; + Component.prototype.componentDidUpdate = safeOverride( + componentDidUpdate, + function componentDidUpdate() { + this._handleLayout(); + } + ); + + Component.prototype.componentWillUnmount = safeOverride( + componentWillUnmount, + function componentWillUnmount() { + this._isMounted = false; + delete registry[this._onLayoutId]; + } + ); Component.prototype._handleLayout = function() { const layout = this._layoutState; @@ -30,13 +72,14 @@ const applyLayout = Component => { if (onLayout) { this.measure((x, y, width, height) => { + if (!this._isMounted) return; + if ( layout.x !== x || layout.y !== y || layout.width !== width || layout.height !== height ) { - const nextLayout = { x, y, width, height }; - const nativeEvent = { layout: nextLayout }; + this._layoutState = { x, y, width, height }; + const nativeEvent = { layout: this._layoutState }; onLayout({ nativeEvent, timeStamp: Date.now() }); - this._layoutState = nextLayout; } }); } diff --git a/yarn.lock b/yarn.lock index 6d19c5de..448c5ab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -303,7 +303,7 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@^2.0.5, asap@~2.0.3: +asap@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"