Skip to content

Commit

Permalink
Split up AnimatedImplementation.js
Browse files Browse the repository at this point in the history
Summary:
AnimatedImplementation.js is getting pretty hard to navigate and reason about so I split it up into different modules. Also took the opportunity to run prettier on that code and do some minor const/let refactorings. This doesn't change any logic, mostly just moves code around and add proper imports / exports.

This opens the door for further cleanup and flow type improvements but want to keep this already big PR as small as possible.

Discussion points:
- Should I use haste for this? Animated is pretty much a standalone module, it still uses a few haste imports but for new modules I used commonjs imports to avoid polluting the haste global namespace too much. The new modules are all internal to Animated and should not be imported externally.
- We're using `requestAnimationFrame` from fbjs instead of the one available globally in RN and browsers is there a reason for that?
- Should we even support web in this implementation? There is a standalone repo that exist for Animated web. Is this implementation of Animated web used internally at facebook?
- Probably still related to web, we used some weird Set polyfill is that still needed?

Notes:
There is a small regression for docs where the type of some classes that are exported like AnimatedValue show up as CallExpression instead if Class.

<img width="655" alt="screen shot 2017-08-14 at 3 19 18 am" src="https://user-images.githubusercontent.com/2677334/29261820-8f243036-809f-11e7-8bf0-0fe9f93939ca.png">

**Test plan**
Tested that all Animated related examples still work in RNTester on iOS and Android.
Tested that the doc is still working
Ran unit tests
Closes facebook#15485

Differential Revision: D5679886

Pulled By: sahrens

fbshipit-source-id: d3e9b6987ab5ff2cd20108c3b9d860c7536be8a0
  • Loading branch information
janicduplessis authored and facebook-github-bot committed Aug 23, 2017
1 parent 950c2b2 commit 5590b1b
Show file tree
Hide file tree
Showing 23 changed files with 2,968 additions and 2,401 deletions.
195 changes: 195 additions & 0 deletions Libraries/Animated/src/AnimatedEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';

const AnimatedValue = require('./nodes/AnimatedValue');
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
const ReactNative = require('ReactNative');

const invariant = require('fbjs/lib/invariant');
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');

export type Mapping = {[key: string]: Mapping} | AnimatedValue;
export type EventConfig = {
listener?: ?Function,
useNativeDriver?: boolean,
};

function attachNativeEvent(
viewRef: any,
eventName: string,
argMapping: Array<?Mapping>,
) {
// Find animated values in `argMapping` and create an array representing their
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
const eventMappings = [];

const traverse = (value, path) => {
if (value instanceof AnimatedValue) {
value.__makeNative();

eventMappings.push({
nativeEventPath: path,
animatedValueTag: value.__getNativeTag(),
});
} else if (typeof value === 'object') {
for (const key in value) {
traverse(value[key], path.concat(key));
}
}
};

invariant(
argMapping[0] && argMapping[0].nativeEvent,
'Native driven events only support animated values contained inside `nativeEvent`.',
);

// Assume that the event containing `nativeEvent` is always the first argument.
traverse(argMapping[0].nativeEvent, []);

const viewTag = ReactNative.findNodeHandle(viewRef);

eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.addAnimatedEventToView(
viewTag,
eventName,
mapping,
);
});

return {
detach() {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.removeAnimatedEventFromView(
viewTag,
eventName,
mapping.animatedValueTag,
);
});
},
};
}

class AnimatedEvent {
_argMapping: Array<?Mapping>;
_listeners: Array<Function> = [];
_callListeners: Function;
_attachedEvent: ?{
detach: () => void,
};
__isNative: boolean;

constructor(argMapping: Array<?Mapping>, config?: EventConfig = {}) {
this._argMapping = argMapping;
if (config.listener) {
this.__addListener(config.listener);
}
this._callListeners = this._callListeners.bind(this);
this._attachedEvent = null;
this.__isNative = shouldUseNativeDriver(config);

if (__DEV__) {
this._validateMapping();
}
}

__addListener(callback: Function): void {
this._listeners.push(callback);
}

__removeListener(callback: Function): void {
this._listeners = this._listeners.filter(listener => listener !== callback);
}

__attach(viewRef: any, eventName: string) {
invariant(
this.__isNative,
'Only native driven events need to be attached.',
);

this._attachedEvent = attachNativeEvent(
viewRef,
eventName,
this._argMapping,
);
}

__detach(viewTag: any, eventName: string) {
invariant(
this.__isNative,
'Only native driven events need to be detached.',
);

this._attachedEvent && this._attachedEvent.detach();
}

__getHandler() {
if (this.__isNative) {
return this._callListeners;
}

return (...args: any) => {
const traverse = (recMapping, recEvt, key) => {
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
recMapping.setValue(recEvt);
} else if (typeof recMapping === 'object') {
for (const mappingKey in recMapping) {
/* $FlowFixMe(>=0.53.0 site=react_native_fb) This comment
* suppresses an error found when Flow v0.53 was deployed. To see
* the error delete this comment and run Flow. */
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
}
};

if (!this.__isNative) {
this._argMapping.forEach((mapping, idx) => {
traverse(mapping, args[idx], 'arg' + idx);
});
}
this._callListeners(...args);
};
}

_callListeners(...args) {
this._listeners.forEach(listener => listener(...args));
}

_validateMapping() {
const traverse = (recMapping, recEvt, key) => {
if (typeof recEvt === 'number') {
invariant(
recMapping instanceof AnimatedValue,
'Bad mapping of type ' +
typeof recMapping +
' for key ' +
key +
', event value must map to AnimatedValue',
);
return;
}
invariant(
typeof recMapping === 'object',
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
);
invariant(
typeof recEvt === 'object',
'Bad event of type ' + typeof recEvt + ' for key ' + key,
);
for (const mappingKey in recMapping) {
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
};
}
}

module.exports = {AnimatedEvent, attachNativeEvent};
Loading

0 comments on commit 5590b1b

Please sign in to comment.