Skip to content

Commit 5590b1b

Browse files
janicduplessisfacebook-github-bot
authored andcommitted
Split up AnimatedImplementation.js
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 #15485 Differential Revision: D5679886 Pulled By: sahrens fbshipit-source-id: d3e9b6987ab5ff2cd20108c3b9d860c7536be8a0
1 parent 950c2b2 commit 5590b1b

23 files changed

Lines changed: 2968 additions & 2401 deletions
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
* @format
11+
*/
12+
'use strict';
13+
14+
const AnimatedValue = require('./nodes/AnimatedValue');
15+
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
16+
const ReactNative = require('ReactNative');
17+
18+
const invariant = require('fbjs/lib/invariant');
19+
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');
20+
21+
export type Mapping = {[key: string]: Mapping} | AnimatedValue;
22+
export type EventConfig = {
23+
listener?: ?Function,
24+
useNativeDriver?: boolean,
25+
};
26+
27+
function attachNativeEvent(
28+
viewRef: any,
29+
eventName: string,
30+
argMapping: Array<?Mapping>,
31+
) {
32+
// Find animated values in `argMapping` and create an array representing their
33+
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
34+
const eventMappings = [];
35+
36+
const traverse = (value, path) => {
37+
if (value instanceof AnimatedValue) {
38+
value.__makeNative();
39+
40+
eventMappings.push({
41+
nativeEventPath: path,
42+
animatedValueTag: value.__getNativeTag(),
43+
});
44+
} else if (typeof value === 'object') {
45+
for (const key in value) {
46+
traverse(value[key], path.concat(key));
47+
}
48+
}
49+
};
50+
51+
invariant(
52+
argMapping[0] && argMapping[0].nativeEvent,
53+
'Native driven events only support animated values contained inside `nativeEvent`.',
54+
);
55+
56+
// Assume that the event containing `nativeEvent` is always the first argument.
57+
traverse(argMapping[0].nativeEvent, []);
58+
59+
const viewTag = ReactNative.findNodeHandle(viewRef);
60+
61+
eventMappings.forEach(mapping => {
62+
NativeAnimatedHelper.API.addAnimatedEventToView(
63+
viewTag,
64+
eventName,
65+
mapping,
66+
);
67+
});
68+
69+
return {
70+
detach() {
71+
eventMappings.forEach(mapping => {
72+
NativeAnimatedHelper.API.removeAnimatedEventFromView(
73+
viewTag,
74+
eventName,
75+
mapping.animatedValueTag,
76+
);
77+
});
78+
},
79+
};
80+
}
81+
82+
class AnimatedEvent {
83+
_argMapping: Array<?Mapping>;
84+
_listeners: Array<Function> = [];
85+
_callListeners: Function;
86+
_attachedEvent: ?{
87+
detach: () => void,
88+
};
89+
__isNative: boolean;
90+
91+
constructor(argMapping: Array<?Mapping>, config?: EventConfig = {}) {
92+
this._argMapping = argMapping;
93+
if (config.listener) {
94+
this.__addListener(config.listener);
95+
}
96+
this._callListeners = this._callListeners.bind(this);
97+
this._attachedEvent = null;
98+
this.__isNative = shouldUseNativeDriver(config);
99+
100+
if (__DEV__) {
101+
this._validateMapping();
102+
}
103+
}
104+
105+
__addListener(callback: Function): void {
106+
this._listeners.push(callback);
107+
}
108+
109+
__removeListener(callback: Function): void {
110+
this._listeners = this._listeners.filter(listener => listener !== callback);
111+
}
112+
113+
__attach(viewRef: any, eventName: string) {
114+
invariant(
115+
this.__isNative,
116+
'Only native driven events need to be attached.',
117+
);
118+
119+
this._attachedEvent = attachNativeEvent(
120+
viewRef,
121+
eventName,
122+
this._argMapping,
123+
);
124+
}
125+
126+
__detach(viewTag: any, eventName: string) {
127+
invariant(
128+
this.__isNative,
129+
'Only native driven events need to be detached.',
130+
);
131+
132+
this._attachedEvent && this._attachedEvent.detach();
133+
}
134+
135+
__getHandler() {
136+
if (this.__isNative) {
137+
return this._callListeners;
138+
}
139+
140+
return (...args: any) => {
141+
const traverse = (recMapping, recEvt, key) => {
142+
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
143+
recMapping.setValue(recEvt);
144+
} else if (typeof recMapping === 'object') {
145+
for (const mappingKey in recMapping) {
146+
/* $FlowFixMe(>=0.53.0 site=react_native_fb) This comment
147+
* suppresses an error found when Flow v0.53 was deployed. To see
148+
* the error delete this comment and run Flow. */
149+
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
150+
}
151+
}
152+
};
153+
154+
if (!this.__isNative) {
155+
this._argMapping.forEach((mapping, idx) => {
156+
traverse(mapping, args[idx], 'arg' + idx);
157+
});
158+
}
159+
this._callListeners(...args);
160+
};
161+
}
162+
163+
_callListeners(...args) {
164+
this._listeners.forEach(listener => listener(...args));
165+
}
166+
167+
_validateMapping() {
168+
const traverse = (recMapping, recEvt, key) => {
169+
if (typeof recEvt === 'number') {
170+
invariant(
171+
recMapping instanceof AnimatedValue,
172+
'Bad mapping of type ' +
173+
typeof recMapping +
174+
' for key ' +
175+
key +
176+
', event value must map to AnimatedValue',
177+
);
178+
return;
179+
}
180+
invariant(
181+
typeof recMapping === 'object',
182+
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
183+
);
184+
invariant(
185+
typeof recEvt === 'object',
186+
'Bad event of type ' + typeof recEvt + ' for key ' + key,
187+
);
188+
for (const mappingKey in recMapping) {
189+
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
190+
}
191+
};
192+
}
193+
}
194+
195+
module.exports = {AnimatedEvent, attachNativeEvent};

0 commit comments

Comments
 (0)