mirror of
https://github.com/status-im/react-native.git
synced 2025-02-14 18:36:35 +00:00
Summary: Includes React Native and its dependencies Fresco, Metro, and Yoga. Excludes samples/examples/docs. find: ^(?:( *)|( *(?:[\*~#]|::))( )? *)?Copyright (?:\(c\) )?(\d{4})\b.+Facebook[\s\S]+?BSD[\s\S]+?(?:this source tree|the same directory)\.$ replace: $1$2$3Copyright (c) $4-present, Facebook, Inc.\n$2\n$1$2$3This source code is licensed under the MIT license found in the\n$1$2$3LICENSE file in the root directory of this source tree. Reviewed By: TheSavior, yungsters Differential Revision: D7007050 fbshipit-source-id: 37dd6bf0ffec0923bfc99c260bb330683f35553e
208 lines
6.5 KiB
JavaScript
208 lines
6.5 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @providesModule createAnimatedComponent
|
|
* @flow
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
const {AnimatedEvent} = require('./AnimatedEvent');
|
|
const AnimatedProps = require('./nodes/AnimatedProps');
|
|
const React = require('React');
|
|
const ViewStylePropTypes = require('ViewStylePropTypes');
|
|
|
|
const invariant = require('fbjs/lib/invariant');
|
|
|
|
function createAnimatedComponent(Component: any): any {
|
|
invariant(
|
|
typeof Component === 'string' ||
|
|
(Component.prototype && Component.prototype.isReactComponent),
|
|
'`createAnimatedComponent` does not support stateless functional components; ' +
|
|
'use a class component instead.',
|
|
);
|
|
|
|
class AnimatedComponent extends React.Component<Object> {
|
|
_component: any;
|
|
_invokeAnimatedPropsCallbackOnMount: boolean = false;
|
|
_prevComponent: any;
|
|
_propsAnimated: AnimatedProps;
|
|
_eventDetachers: Array<Function> = [];
|
|
_setComponentRef: Function;
|
|
|
|
static __skipSetNativeProps_FOR_TESTS_ONLY = false;
|
|
|
|
constructor(props: Object) {
|
|
super(props);
|
|
this._setComponentRef = this._setComponentRef.bind(this);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this._propsAnimated && this._propsAnimated.__detach();
|
|
this._detachNativeEvents();
|
|
}
|
|
|
|
setNativeProps(props) {
|
|
this._component.setNativeProps(props);
|
|
}
|
|
|
|
UNSAFE_componentWillMount() {
|
|
this._attachProps(this.props);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this._invokeAnimatedPropsCallbackOnMount) {
|
|
this._invokeAnimatedPropsCallbackOnMount = false;
|
|
this._animatedPropsCallback();
|
|
}
|
|
|
|
this._propsAnimated.setNativeView(this._component);
|
|
this._attachNativeEvents();
|
|
}
|
|
|
|
_attachNativeEvents() {
|
|
// Make sure to get the scrollable node for components that implement
|
|
// `ScrollResponder.Mixin`.
|
|
const scrollableNode = this._component.getScrollableNode
|
|
? this._component.getScrollableNode()
|
|
: this._component;
|
|
|
|
for (const key in this.props) {
|
|
const prop = this.props[key];
|
|
if (prop instanceof AnimatedEvent && prop.__isNative) {
|
|
prop.__attach(scrollableNode, key);
|
|
this._eventDetachers.push(() => prop.__detach(scrollableNode, key));
|
|
}
|
|
}
|
|
}
|
|
|
|
_detachNativeEvents() {
|
|
this._eventDetachers.forEach(remove => remove());
|
|
this._eventDetachers = [];
|
|
}
|
|
|
|
// The system is best designed when setNativeProps is implemented. It is
|
|
// able to avoid re-rendering and directly set the attributes that changed.
|
|
// However, setNativeProps can only be implemented on leaf native
|
|
// components. If you want to animate a composite component, you need to
|
|
// re-render it. In this case, we have a fallback that uses forceUpdate.
|
|
_animatedPropsCallback = () => {
|
|
if (this._component == null) {
|
|
// AnimatedProps is created in will-mount because it's used in render.
|
|
// But this callback may be invoked before mount in async mode,
|
|
// In which case we should defer the setNativeProps() call.
|
|
// React may throw away uncommitted work in async mode,
|
|
// So a deferred call won't always be invoked.
|
|
this._invokeAnimatedPropsCallbackOnMount = true;
|
|
} else if (
|
|
AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY ||
|
|
typeof this._component.setNativeProps !== 'function'
|
|
) {
|
|
this.forceUpdate();
|
|
} else if (!this._propsAnimated.__isNative) {
|
|
this._component.setNativeProps(
|
|
this._propsAnimated.__getAnimatedValue(),
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
'Attempting to run JS driven animation on animated ' +
|
|
'node that has been moved to "native" earlier by starting an ' +
|
|
'animation with `useNativeDriver: true`',
|
|
);
|
|
}
|
|
};
|
|
|
|
_attachProps(nextProps) {
|
|
const oldPropsAnimated = this._propsAnimated;
|
|
|
|
this._propsAnimated = new AnimatedProps(
|
|
nextProps,
|
|
this._animatedPropsCallback,
|
|
);
|
|
|
|
// When you call detach, it removes the element from the parent list
|
|
// of children. If it goes to 0, then the parent also detaches itself
|
|
// and so on.
|
|
// An optimization is to attach the new elements and THEN detach the old
|
|
// ones instead of detaching and THEN attaching.
|
|
// This way the intermediate state isn't to go to 0 and trigger
|
|
// this expensive recursive detaching to then re-attach everything on
|
|
// the very next operation.
|
|
oldPropsAnimated && oldPropsAnimated.__detach();
|
|
}
|
|
|
|
UNSAFE_componentWillReceiveProps(newProps) {
|
|
this._attachProps(newProps);
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (this._component !== this._prevComponent) {
|
|
this._propsAnimated.setNativeView(this._component);
|
|
}
|
|
if (this._component !== this._prevComponent || prevProps !== this.props) {
|
|
this._detachNativeEvents();
|
|
this._attachNativeEvents();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const props = this._propsAnimated.__getValue();
|
|
return (
|
|
<Component
|
|
{...props}
|
|
ref={this._setComponentRef}
|
|
// The native driver updates views directly through the UI thread so we
|
|
// have to make sure the view doesn't get optimized away because it cannot
|
|
// go through the NativeViewHierarchyManager since it operates on the shadow
|
|
// thread.
|
|
collapsable={
|
|
this._propsAnimated.__isNative ? false : props.collapsable
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
_setComponentRef(c) {
|
|
this._prevComponent = this._component;
|
|
this._component = c;
|
|
}
|
|
|
|
// A third party library can use getNode()
|
|
// to get the node reference of the decorated component
|
|
getNode() {
|
|
return this._component;
|
|
}
|
|
}
|
|
|
|
const propTypes = Component.propTypes;
|
|
|
|
AnimatedComponent.propTypes = {
|
|
style: function(props, propName, componentName) {
|
|
if (!propTypes) {
|
|
return;
|
|
}
|
|
|
|
for (const key in ViewStylePropTypes) {
|
|
if (!propTypes[key] && props[key] !== undefined) {
|
|
console.warn(
|
|
'You are setting the style `{ ' +
|
|
key +
|
|
': ... }` as a prop. You ' +
|
|
'should nest it in a style object. ' +
|
|
'E.g. `{ style: { ' +
|
|
key +
|
|
': ... } }`',
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
return AnimatedComponent;
|
|
}
|
|
|
|
module.exports = createAnimatedComponent;
|