mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e706721490
* Update Flow to 0.84 * Fix violations * Use inexact object syntax in files from fbsource * Fix warning extraction to use a modern parser * Codemod inexact objects to new syntax * Tighten types that can be exact * Revert unintentional formatting changes from codemod
159 lines
4.5 KiB
JavaScript
159 lines
4.5 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
import React from 'react';
|
|
import invariant from 'shared/invariant';
|
|
|
|
type Unsubscribe = () => void;
|
|
|
|
export function createSubscription<Property, Value>(
|
|
config: $ReadOnly<{|
|
|
// Synchronously gets the value for the subscribed property.
|
|
// Return undefined if the subscribable value is undefined,
|
|
// Or does not support synchronous reading (e.g. native Promise).
|
|
getCurrentValue: (source: Property) => Value | void,
|
|
|
|
// Setup a subscription for the subscribable value in props, and return an unsubscribe function.
|
|
// Return empty function if the property cannot be unsubscribed from (e.g. native Promises).
|
|
// Due to the variety of change event types, subscribers should provide their own handlers.
|
|
// Those handlers should not attempt to update state though;
|
|
// They should call the callback() instead when a subscription changes.
|
|
subscribe: (
|
|
source: Property,
|
|
callback: (value: Value | void) => void,
|
|
) => Unsubscribe,
|
|
|}>,
|
|
): React$ComponentType<{|
|
|
children: (value: Value | void) => React$Node,
|
|
source: Property,
|
|
|}> {
|
|
const {getCurrentValue, subscribe} = config;
|
|
|
|
if (__DEV__) {
|
|
if (typeof getCurrentValue !== 'function') {
|
|
console.error('Subscription must specify a getCurrentValue function');
|
|
}
|
|
if (typeof subscribe !== 'function') {
|
|
console.error('Subscription must specify a subscribe function');
|
|
}
|
|
}
|
|
|
|
type Props = {|
|
|
children: (value: Value) => React$Element<any>,
|
|
source: Property,
|
|
|};
|
|
type State = {|
|
|
source: Property,
|
|
value: Value | void,
|
|
|};
|
|
|
|
// Reference: https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3
|
|
class Subscription extends React.Component<Props, State> {
|
|
state: State = {
|
|
source: this.props.source,
|
|
value:
|
|
this.props.source != null
|
|
? getCurrentValue(this.props.source)
|
|
: undefined,
|
|
};
|
|
|
|
_hasUnmounted: boolean = false;
|
|
_unsubscribe: Unsubscribe | null = null;
|
|
|
|
static getDerivedStateFromProps(nextProps, prevState) {
|
|
if (nextProps.source !== prevState.source) {
|
|
return {
|
|
source: nextProps.source,
|
|
value:
|
|
nextProps.source != null
|
|
? getCurrentValue(nextProps.source)
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.subscribe();
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
if (this.state.source !== prevState.source) {
|
|
this.unsubscribe();
|
|
this.subscribe();
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsubscribe();
|
|
|
|
// Track mounted to avoid calling setState after unmounting
|
|
// For source like Promises that can't be unsubscribed from.
|
|
this._hasUnmounted = true;
|
|
}
|
|
|
|
render() {
|
|
return this.props.children(this.state.value);
|
|
}
|
|
|
|
subscribe() {
|
|
const {source} = this.state;
|
|
if (source != null) {
|
|
const callback = (value: Value | void) => {
|
|
if (this._hasUnmounted) {
|
|
return;
|
|
}
|
|
|
|
this.setState(state => {
|
|
// If the value is the same, skip the unnecessary state update.
|
|
if (value === state.value) {
|
|
return null;
|
|
}
|
|
|
|
// If this event belongs to an old or uncommitted data source, ignore it.
|
|
if (source !== state.source) {
|
|
return null;
|
|
}
|
|
|
|
return {value};
|
|
});
|
|
};
|
|
|
|
// Store the unsubscribe method for later (in case the subscribable prop changes).
|
|
const unsubscribe = subscribe(source, callback);
|
|
invariant(
|
|
typeof unsubscribe === 'function',
|
|
'A subscription must return an unsubscribe function.',
|
|
);
|
|
|
|
// It's safe to store unsubscribe on the instance because
|
|
// We only read or write that property during the "commit" phase.
|
|
this._unsubscribe = unsubscribe;
|
|
|
|
// External values could change between render and mount,
|
|
// In some cases it may be important to handle this case.
|
|
const value = getCurrentValue(this.props.source);
|
|
if (value !== this.state.value) {
|
|
this.setState({value});
|
|
}
|
|
}
|
|
}
|
|
|
|
unsubscribe() {
|
|
if (typeof this._unsubscribe === 'function') {
|
|
this._unsubscribe();
|
|
}
|
|
this._unsubscribe = null;
|
|
}
|
|
}
|
|
|
|
return Subscription;
|
|
}
|