-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add hooks variant of createAnimatedComponent #24321
Closed
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1fa577c
createAnimatedComponent -> hooks
Jyrno42 bb32a20
got rid of the getScrollableNode issue
Jyrno42 d4f2d53
Move Hook based createAnimatedComponent to a new file and behind a flag
Jyrno42 ff1ff55
Add logic for getNode via forwardRef
Jyrno42 758a6c4
s/AnimatedOnAFeeling/createAnimatedComponentWithHooks/
Jyrno42 11e72a5
Use hooks based animated component by default
Jyrno42 ed88e92
Use useImperativeHandle to pass getNode up
Jyrno42 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletions
202
Libraries/Animated/src/createAnimatedComponentWithHooks.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
* @format | ||
*/ | ||
'use strict'; | ||
|
||
const {AnimatedEvent} = require('./AnimatedEvent'); | ||
const AnimatedProps = require('./nodes/AnimatedProps'); | ||
const React = require('React'); | ||
const DeprecatedViewStylePropTypes = require('DeprecatedViewStylePropTypes'); | ||
|
||
const {useCallback, useEffect, useImperativeHandle, useState} = React; | ||
|
||
const invariant = require('invariant'); | ||
|
||
function createAnimatedComponentWithHooks(Component: any): any { | ||
invariant( | ||
typeof Component !== 'function' || | ||
(Component.prototype && Component.prototype.isReactComponent), | ||
'`createAnimatedComponent` does not support stateless functional components; ' + | ||
'use a class component instead.', | ||
); | ||
|
||
const AnimatedComponent = (props: any, forwardedRef?: any) => { | ||
const [initialized, setInitialized] = useState(false); | ||
const [, forceUpdate] = useState(); | ||
|
||
let _invokeAnimatedPropsCallbackOnMount: boolean = false; | ||
let _component: any = null; | ||
|
||
let _eventDetachers: Array<Function> = []; | ||
|
||
const _attachNativeEvents = () => { | ||
// Make sure to get the scrollable node for components that implement | ||
// `ScrollResponder.Mixin`. | ||
const scrollableNode = _component.getScrollableNode | ||
? _component.getScrollableNode() | ||
: _component; | ||
|
||
for (const key in props) { | ||
const prop = props[key]; | ||
if (prop instanceof AnimatedEvent && prop.__isNative) { | ||
prop.__attach(scrollableNode, key); | ||
_eventDetachers.push(() => prop.__detach(scrollableNode, key)); | ||
} | ||
} | ||
}; | ||
|
||
const _detachNativeEvents = () => { | ||
_eventDetachers.forEach(remove => remove()); | ||
_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. | ||
const _animatedPropsCallback = () => { | ||
if (_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. | ||
_invokeAnimatedPropsCallbackOnMount = true; | ||
} else if ( | ||
AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY || | ||
typeof _component.setNativeProps !== 'function' | ||
) { | ||
forceUpdate(); | ||
} else if (!_propsAnimated.__isNative) { | ||
_component.setNativeProps(_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`', | ||
); | ||
} | ||
}; | ||
|
||
const _setComponentRef = useCallback( | ||
node => { | ||
if (node !== null) { | ||
let _prevComponent = _component; | ||
_component = node; | ||
|
||
if (_component !== _prevComponent) { | ||
_propsAnimated.setNativeView(_component); | ||
|
||
if (!initialized) { | ||
if (_invokeAnimatedPropsCallbackOnMount) { | ||
_invokeAnimatedPropsCallbackOnMount = false; | ||
_animatedPropsCallback(); | ||
} | ||
|
||
_propsAnimated.setNativeView(_component); | ||
_attachNativeEvents(); | ||
} | ||
} | ||
} | ||
}, | ||
[initialized], | ||
); | ||
|
||
useEffect(() => { | ||
if (!initialized) { | ||
setInitialized(true); | ||
} | ||
|
||
return () => { | ||
_detachNativeEvents(); | ||
|
||
_propsAnimated && _propsAnimated.__detach(); | ||
}; | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (_component) { | ||
_detachNativeEvents(); | ||
_attachNativeEvents(); | ||
} | ||
|
||
const oldPropsAnimated = _propsAnimated; | ||
|
||
_propsAnimated = new AnimatedProps(props, _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(); | ||
}, [props]); | ||
|
||
useImperativeHandle(forwardedRef, () => ({ | ||
getNode: () => { | ||
return _component; | ||
}, | ||
})); | ||
|
||
let _propsAnimated: AnimatedProps = new AnimatedProps( | ||
props, | ||
_animatedPropsCallback, | ||
); | ||
const animatedProps = _propsAnimated.__getValue(); | ||
|
||
return ( | ||
<Component | ||
{...animatedProps} | ||
ref={_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={ | ||
_propsAnimated.__isNative ? false : animatedProps.collapsable | ||
} | ||
/> | ||
); | ||
}; | ||
|
||
const propTypes = Component.propTypes; | ||
|
||
const AnimatedComponentWithRef = React.forwardRef(AnimatedComponent); | ||
AnimatedComponentWithRef.displayName = 'AnimatedComponent'; | ||
|
||
AnimatedComponentWithRef.propTypes = { | ||
style: function(props, propName, componentName) { | ||
if (!propTypes) { | ||
return; | ||
} | ||
|
||
for (const key in DeprecatedViewStylePropTypes) { | ||
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 AnimatedComponentWithRef; | ||
} | ||
|
||
module.exports = createAnimatedComponentWithHooks; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot assign object literal to
AnimatedComponentWithRef.propTypes
because propertypropTypes
is missing inReact.AbstractComponentStatics
[1].