create-subscription (#12325)
create-subscription provides an simple, async-safe interface to manage a subscription.
This commit is contained in:
parent
ad9544f48e
commit
00a0e3c14f
|
@ -0,0 +1,184 @@
|
|||
# create-subscription
|
||||
|
||||
`create-subscription` provides an async-safe interface to manage a subscription.
|
||||
|
||||
## When should you NOT use this?
|
||||
|
||||
This utility should be used for subscriptions to a single value that are typically only read in one place and may update frequently (e.g. a component that subscribes to a geolocation API to show a dot on a map).
|
||||
|
||||
Other cases have **better long-term solutions**:
|
||||
* Redux/Flux stores should use the [context API](https://reactjs.org/docs/context.html) instead.
|
||||
* I/O subscriptions (e.g. notifications) that update infrequently should use [`simple-cache-provider`](https://github.com/facebook/react/blob/master/packages/simple-cache-provider/README.md) instead.
|
||||
* Complex libraries like Relay/Apollo should manage subscriptions manually with the same techniques which this library uses under the hood (as referenced [here](https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3)) in a way that is most optimized for their library usage.
|
||||
|
||||
## What types of subscriptions can this support?
|
||||
|
||||
This abstraction can handle a variety of subscription types, including:
|
||||
* Event dispatchers like `HTMLInputElement`.
|
||||
* Custom pub/sub components like Relay's `FragmentSpecResolver`.
|
||||
* Observable types like RxJS `BehaviorSubject` and `ReplaySubject`. (Types like RxJS `Subject` or `Observable` are not supported, because they provide no way to read the "current" value after it has been emitted.)
|
||||
* Native Promises.
|
||||
|
||||
# Installation
|
||||
|
||||
```sh
|
||||
# Yarn
|
||||
yarn add create-subscription
|
||||
|
||||
# NPM
|
||||
npm install create-subscription --save
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
To configure a subscription, you must provide two methods: `getCurrentValue` and `subscribe`.
|
||||
|
||||
```js
|
||||
import { createSubscription } from "create-subscription";
|
||||
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue(source) {
|
||||
// Return the current value of the subscription (source),
|
||||
// or `undefined` if the value can't be read synchronously (e.g. native Promises).
|
||||
},
|
||||
subscribe(source, callback) {
|
||||
// Subscribe (e.g. add an event listener) to the subscription (source).
|
||||
// Call callback(newValue) whenever a subscription changes.
|
||||
// Return an unsubscribe method,
|
||||
// Or a no-op if unsubscribe is not supported (e.g. native Promises).
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
To use the `Subscription` component, pass the subscribable property (e.g. an event dispatcher, Flux store, observable) as the `source` property and use a [render prop](https://reactjs.org/docs/render-props.html), `children`, to handle the subscribed value when it changes:
|
||||
|
||||
```js
|
||||
<Subscription source={eventDispatcher}>
|
||||
{value => <AnotherComponent value={value} />}
|
||||
</Subscription>
|
||||
```
|
||||
|
||||
# Examples
|
||||
|
||||
This API can be used to subscribe to a variety of "subscribable" sources, from event dispatchers to RxJS observables. Below are a few examples of how to subscribe to common types.
|
||||
|
||||
## Subscribing to event dispatchers
|
||||
|
||||
Below is an example showing how `create-subscription` can be used to subscribe to event dispatchers such as DOM elements.
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
import { createSubscription } from "create-subscription";
|
||||
|
||||
// Start with a simple component.
|
||||
// In this case, it's a functional component, but it could have been a class.
|
||||
function FollowerComponent({ followersCount }) {
|
||||
return <div>You have {followersCount} followers!</div>;
|
||||
}
|
||||
|
||||
// Create a wrapper component to manage the subscription.
|
||||
const EventHandlerSubscription = createSubscription({
|
||||
getCurrentValue: eventDispatcher => eventDispatcher.value,
|
||||
subscribe: (eventDispatcher, callback) => {
|
||||
const onChange = event => callback(eventDispatcher.value);
|
||||
eventDispatcher.addEventListener("change", onChange);
|
||||
return () => eventDispatcher.removeEventListener("change", onChange);
|
||||
}
|
||||
});
|
||||
|
||||
// Your component can now be used as shown below.
|
||||
// In this example, 'eventDispatcher' represents a generic event dispatcher.
|
||||
<EventHandlerSubscription source={eventDispatcher}>
|
||||
{value => <FollowerComponent followersCount={value} />}
|
||||
</EventHandlerSubscription>;
|
||||
```
|
||||
|
||||
## Subscribing to observables
|
||||
|
||||
Below are examples showing how `create-subscription` can be used to subscribe to certain types of observables (e.g. RxJS `BehaviorSubject` and `ReplaySubject`).
|
||||
|
||||
**Note** that it is not possible to support all observable types (e.g. RxJS `Subject` or `Observable`) because some provide no way to read the "current" value after it has been emitted.
|
||||
|
||||
### `BehaviorSubject`
|
||||
```js
|
||||
const BehaviorSubscription = createSubscription({
|
||||
getCurrentValue: behaviorSubject => behaviorSubject.getValue(),
|
||||
subscribe: (behaviorSubject, callback) => {
|
||||
const subscription = behaviorSubject.subscribe(callback);
|
||||
return () => subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `ReplaySubject`
|
||||
```js
|
||||
const ReplaySubscription = createSubscription({
|
||||
getCurrentValue: replaySubject => {
|
||||
let currentValue;
|
||||
// ReplaySubject does not have a sync data getter,
|
||||
// So we need to temporarily subscribe to retrieve the most recent value.
|
||||
replaySubject
|
||||
.subscribe(value => {
|
||||
currentValue = value;
|
||||
})
|
||||
.unsubscribe();
|
||||
return currentValue;
|
||||
},
|
||||
subscribe: (replaySubject, callback) => {
|
||||
const subscription = replaySubject.subscribe(callback);
|
||||
return () => subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Subscribing to a Promise
|
||||
|
||||
Below is an example showing how `create-subscription` can be used with native Promises.
|
||||
|
||||
**Note** that it an initial render value of `undefined` is unavoidable due to the fact that Promises provide no way to synchronously read their current value.
|
||||
|
||||
**Note** the lack of a way to "unsubscribe" from a Promise can result in memory leaks as long as something has a reference to the Promise. This should be taken into considerationg when determining whether Promises are appropriate to use in this way within your application.
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
import { createSubscription } from "create-subscription";
|
||||
|
||||
// Start with a simple component.
|
||||
function LoadingComponent({ loadingStatus }) {
|
||||
if (loadingStatus === undefined) {
|
||||
// Loading
|
||||
} else if (loadingStatus === null) {
|
||||
// Error
|
||||
} else {
|
||||
// Success
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the functional component with a subscriber HOC.
|
||||
// This HOC will manage subscriptions and pass values to the decorated component.
|
||||
// It will add and remove subscriptions in an async-safe way when props change.
|
||||
const PromiseSubscription = createSubscription({
|
||||
getCurrentValue: promise => {
|
||||
// There is no way to synchronously read a Promise's value,
|
||||
// So this method should return undefined.
|
||||
return undefined;
|
||||
},
|
||||
subscribe: (promise, callback) => {
|
||||
promise.then(
|
||||
// Success
|
||||
value => callback(value),
|
||||
// Failure
|
||||
() => callback(null)
|
||||
);
|
||||
|
||||
// There is no way to "unsubscribe" from a Promise.
|
||||
// create-subscription will still prevent stale values from rendering.
|
||||
return () => {};
|
||||
}
|
||||
});
|
||||
|
||||
// Your component can now be used as shown below.
|
||||
<PromiseSubscription source={loadingPromise}>
|
||||
{loadingStatus => <LoadingComponent loadingStatus={loadingStatus} />}
|
||||
</PromiseSubscription>
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export * from './src/createSubscription';
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/create-subscription.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/create-subscription.development.js');
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "create-subscription",
|
||||
"description": "HOC for creating async-safe React components with subscriptions",
|
||||
"version": "0.0.1",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16.3.0-alpha.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rxjs": "^5.5.6"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,457 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let createSubscription;
|
||||
let BehaviorSubject;
|
||||
let ReactFeatureFlags;
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let ReplaySubject;
|
||||
|
||||
describe('createSubscription', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
createSubscription = require('create-subscription').createSubscription;
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
|
||||
BehaviorSubject = require('rxjs/BehaviorSubject').BehaviorSubject;
|
||||
ReplaySubject = require('rxjs/ReplaySubject').ReplaySubject;
|
||||
});
|
||||
|
||||
function createBehaviorSubject(initialValue) {
|
||||
const behaviorSubject = new BehaviorSubject();
|
||||
if (initialValue) {
|
||||
behaviorSubject.next(initialValue);
|
||||
}
|
||||
return behaviorSubject;
|
||||
}
|
||||
|
||||
function createReplaySubject(initialValue) {
|
||||
const replaySubject = new ReplaySubject();
|
||||
if (initialValue) {
|
||||
replaySubject.next(initialValue);
|
||||
}
|
||||
return replaySubject;
|
||||
}
|
||||
|
||||
it('supports basic subscription pattern', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => source.getValue(),
|
||||
subscribe: (source, callback) => {
|
||||
const subscription = source.subscribe(callback);
|
||||
return () => subscription.unsubscribe;
|
||||
},
|
||||
});
|
||||
|
||||
const observable = createBehaviorSubject();
|
||||
ReactNoop.render(
|
||||
<Subscription source={observable}>
|
||||
{(value = 'default') => {
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}}
|
||||
</Subscription>,
|
||||
);
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
observable.next(123);
|
||||
expect(ReactNoop.flush()).toEqual([123]);
|
||||
observable.next('abc');
|
||||
expect(ReactNoop.flush()).toEqual(['abc']);
|
||||
|
||||
// Unmounting the subscriber should remove listeners
|
||||
ReactNoop.render(<div />);
|
||||
observable.next(456);
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should support observable types like RxJS ReplaySubject', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => {
|
||||
let currentValue;
|
||||
source
|
||||
.subscribe(value => {
|
||||
currentValue = value;
|
||||
})
|
||||
.unsubscribe();
|
||||
return currentValue;
|
||||
},
|
||||
subscribe: (source, callback) => {
|
||||
const subscription = source.subscribe(callback);
|
||||
return () => subscription.unsubscribe;
|
||||
},
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const observable = createReplaySubject('initial');
|
||||
|
||||
ReactNoop.render(<Subscription source={observable}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['initial']);
|
||||
observable.next('updated');
|
||||
expect(ReactNoop.flush()).toEqual(['updated']);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
ReactNoop.render(<Subscription>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
});
|
||||
|
||||
describe('Promises', () => {
|
||||
it('should support Promises', async () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {
|
||||
source.then(value => callback(value), value => callback(value));
|
||||
// (Can't unsubscribe from a Promise)
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
function render(hasLoaded) {
|
||||
if (hasLoaded === undefined) {
|
||||
ReactNoop.yield('loading');
|
||||
} else {
|
||||
ReactNoop.yield(hasLoaded ? 'finished' : 'failed');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let resolveA, rejectB;
|
||||
const promiseA = new Promise((resolve, reject) => {
|
||||
resolveA = resolve;
|
||||
});
|
||||
const promiseB = new Promise((resolve, reject) => {
|
||||
rejectB = reject;
|
||||
});
|
||||
|
||||
// Test a promise that resolves after render
|
||||
ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['loading']);
|
||||
resolveA(true);
|
||||
await promiseA;
|
||||
expect(ReactNoop.flush()).toEqual(['finished']);
|
||||
|
||||
// Test a promise that resolves before render
|
||||
// Note that this will require an extra render anyway,
|
||||
// Because there is no way to syncrhonously get a Promise's value
|
||||
rejectB(false);
|
||||
ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['loading']);
|
||||
await promiseB.catch(() => true);
|
||||
expect(ReactNoop.flush()).toEqual(['failed']);
|
||||
});
|
||||
|
||||
it('should still work if unsubscription is managed incorrectly', async () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {
|
||||
source.then(callback);
|
||||
// (Can't unsubscribe from a Promise)
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
let resolveA, resolveB;
|
||||
const promiseA = new Promise(resolve => (resolveA = resolve));
|
||||
const promiseB = new Promise(resolve => (resolveB = resolve));
|
||||
|
||||
// Subscribe first to Promise A then Promise B
|
||||
ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
|
||||
// Resolve both Promises
|
||||
resolveB(123);
|
||||
resolveA('abc');
|
||||
await Promise.all([promiseA, promiseB]);
|
||||
|
||||
// Ensure that only Promise B causes an update
|
||||
expect(ReactNoop.flush()).toEqual([123]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should unsubscribe from old subscribables and subscribe to new subscribables when props change', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => source.getValue(),
|
||||
subscribe: (source, callback) => {
|
||||
const subscription = source.subscribe(callback);
|
||||
return () => subscription.unsubscribe();
|
||||
},
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const observableA = createBehaviorSubject('a-0');
|
||||
const observableB = createBehaviorSubject('b-0');
|
||||
|
||||
ReactNoop.render(
|
||||
<Subscription source={observableA}>{render}</Subscription>,
|
||||
);
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(ReactNoop.flush()).toEqual(['a-0']);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
ReactNoop.render(
|
||||
<Subscription source={observableB}>{render}</Subscription>,
|
||||
);
|
||||
expect(ReactNoop.flush()).toEqual(['b-0']);
|
||||
|
||||
// Updates to the old subscribable should not re-render the child component
|
||||
observableA.next('a-1');
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
|
||||
// Updates to the bew subscribable should re-render the child component
|
||||
observableB.next('b-1');
|
||||
expect(ReactNoop.flush()).toEqual(['b-1']);
|
||||
});
|
||||
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => source.getValue(),
|
||||
subscribe: (source, callback) => {
|
||||
const subscription = source.subscribe(callback);
|
||||
return () => subscription.unsubscribe();
|
||||
},
|
||||
});
|
||||
|
||||
class Parent extends React.Component {
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.observed !== prevState.observed) {
|
||||
return {
|
||||
observed: nextProps.observed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
log.push('Parent.componentDidMount');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
log.push('Parent.componentDidUpdate');
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
ReactNoop.yield('Subscriber: ' + value);
|
||||
return <Child value={value} />;
|
||||
}}
|
||||
</Subscription>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const observableA = createBehaviorSubject('a-0');
|
||||
const observableB = createBehaviorSubject('b-0');
|
||||
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
expect(ReactNoop.flush()).toEqual(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Start React update, but don't finish
|
||||
ReactNoop.render(<Parent observed={observableB} />);
|
||||
ReactNoop.flushThrough(['Subscriber: b-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Emit some updates from the uncommitted subscribable
|
||||
observableB.next('b-1');
|
||||
observableB.next('b-2');
|
||||
observableB.next('b-3');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the last emitted update to be rendered (because of the commit phase value check)
|
||||
// But the intermediate ones should be ignored,
|
||||
// And the final rendered output should be the higher-priority observable.
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Child: b-0',
|
||||
'Subscriber: b-3',
|
||||
'Child: b-3',
|
||||
'Subscriber: a-0',
|
||||
'Child: a-0',
|
||||
]);
|
||||
expect(log).toEqual([
|
||||
'Parent.componentDidMount',
|
||||
'Parent.componentDidUpdate',
|
||||
'Parent.componentDidUpdate',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not drop values emitted between updates', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => source.getValue(),
|
||||
subscribe: (source, callback) => {
|
||||
const subscription = source.subscribe(callback);
|
||||
return () => subscription.unsubscribe();
|
||||
},
|
||||
});
|
||||
|
||||
class Parent extends React.Component {
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.observed !== prevState.observed) {
|
||||
return {
|
||||
observed: nextProps.observed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
log.push('Parent.componentDidMount');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
log.push('Parent.componentDidUpdate');
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
ReactNoop.yield('Subscriber: ' + value);
|
||||
return <Child value={value} />;
|
||||
}}
|
||||
</Subscription>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const observableA = createBehaviorSubject('a-0');
|
||||
const observableB = createBehaviorSubject('b-0');
|
||||
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
expect(ReactNoop.flush()).toEqual(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Start React update, but don't finish
|
||||
ReactNoop.render(<Parent observed={observableB} />);
|
||||
ReactNoop.flushThrough(['Subscriber: b-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Emit some updates from the old subscribable
|
||||
observableA.next('a-1');
|
||||
observableA.next('a-2');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the new subscribable to finish rendering,
|
||||
// But then the updated values from the old subscribable should be used.
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Child: b-0',
|
||||
'Subscriber: a-2',
|
||||
'Child: a-2',
|
||||
]);
|
||||
expect(log).toEqual([
|
||||
'Parent.componentDidMount',
|
||||
'Parent.componentDidUpdate',
|
||||
'Parent.componentDidUpdate',
|
||||
]);
|
||||
|
||||
// Updates from the new subsribable should be ignored.
|
||||
observableB.next('b-1');
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
expect(log).toEqual([
|
||||
'Parent.componentDidMount',
|
||||
'Parent.componentDidUpdate',
|
||||
'Parent.componentDidUpdate',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('warnings', () => {
|
||||
it('should warn for invalid missing getCurrentValue', () => {
|
||||
expect(() => {
|
||||
createSubscription(
|
||||
{
|
||||
subscribe: () => () => {},
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a getCurrentValue function');
|
||||
});
|
||||
|
||||
it('should warn for invalid missing subscribe', () => {
|
||||
expect(() => {
|
||||
createSubscription(
|
||||
{
|
||||
getCurrentValue: () => () => {},
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a subscribe function');
|
||||
});
|
||||
|
||||
it('should warn if subscribe does not return an unsubscribe method', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {},
|
||||
});
|
||||
|
||||
const observable = createBehaviorSubject();
|
||||
ReactNoop.render(
|
||||
<Subscription source={observable}>{value => null}</Subscription>,
|
||||
);
|
||||
|
||||
expect(ReactNoop.flush).toThrow(
|
||||
'A subscription must return an unsubscribe function.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Copyright (c) 2014-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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
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 false to indicate 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;
|
||||
|
||||
warning(
|
||||
typeof getCurrentValue === 'function',
|
||||
'Subscription must specify a getCurrentValue function',
|
||||
);
|
||||
warning(
|
||||
typeof subscribe === 'function',
|
||||
'Subscription must specify a subscribe function',
|
||||
);
|
||||
|
||||
type Props = {
|
||||
children: (value: Value) => React$Element<any>,
|
||||
source: Property,
|
||||
};
|
||||
type State = {
|
||||
source: Property,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: Unsubscribe | null,
|
||||
},
|
||||
value: Value | void,
|
||||
};
|
||||
|
||||
// Reference: https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3
|
||||
class Subscription extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
source: this.props.source,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: null,
|
||||
},
|
||||
value:
|
||||
this.props.source != null
|
||||
? getCurrentValue(this.props.source)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.source !== prevState.source) {
|
||||
return {
|
||||
source: nextProps.source,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: null,
|
||||
},
|
||||
value:
|
||||
nextProps.source != null
|
||||
? getCurrentValue(nextProps.source)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.source !== prevState.source) {
|
||||
this.unsubscribe(prevState);
|
||||
this.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe(this.state);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children(this.state.value);
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
const {source} = this.state;
|
||||
if (source != null) {
|
||||
const callback = (value: Value | void) => {
|
||||
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 subscription for later (in case it's needed to unsubscribe).
|
||||
// This is safe to do via mutation since:
|
||||
// 1) It does not impact render.
|
||||
// 2) This method will only be called during the "commit" phase.
|
||||
const unsubscribe = subscribe(source, callback);
|
||||
|
||||
invariant(
|
||||
typeof unsubscribe === 'function',
|
||||
'A subscription must return an unsubscribe function.',
|
||||
);
|
||||
|
||||
this.state.unsubscribeContainer.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(state: State) {
|
||||
const {unsubscribe} = state.unsubscribeContainer;
|
||||
if (typeof unsubscribe === 'function') {
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Subscription;
|
||||
}
|
|
@ -254,6 +254,16 @@ const bundles = [
|
|||
global: 'SimpleCacheProvider',
|
||||
externals: ['react'],
|
||||
},
|
||||
|
||||
/******* createComponentWithSubscriptions (experimental) *******/
|
||||
{
|
||||
label: 'create-subscription',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'create-subscription',
|
||||
global: 'createSubscription',
|
||||
externals: ['react'],
|
||||
},
|
||||
];
|
||||
|
||||
// Based on deep-freeze by substack (public domain)
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"filename": "react.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react",
|
||||
"size": 55674,
|
||||
"gzip": 15255
|
||||
"size": 55675,
|
||||
"gzip": 15253
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
|
@ -18,8 +18,8 @@
|
|||
"filename": "react.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react",
|
||||
"size": 46095,
|
||||
"gzip": 12925
|
||||
"size": 46096,
|
||||
"gzip": 12924
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
|
@ -32,8 +32,8 @@
|
|||
"filename": "React-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react",
|
||||
"size": 45476,
|
||||
"gzip": 12448
|
||||
"size": 45477,
|
||||
"gzip": 12446
|
||||
},
|
||||
{
|
||||
"filename": "React-prod.js",
|
||||
|
@ -46,50 +46,50 @@
|
|||
"filename": "react-dom.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 591513,
|
||||
"gzip": 138743
|
||||
"size": 600642,
|
||||
"gzip": 139543
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 96778,
|
||||
"gzip": 31445
|
||||
"size": 100738,
|
||||
"gzip": 32495
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 575526,
|
||||
"gzip": 134516
|
||||
"size": 584651,
|
||||
"gzip": 135289
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 95503,
|
||||
"gzip": 30619
|
||||
"size": 99167,
|
||||
"gzip": 31568
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 594783,
|
||||
"gzip": 136782
|
||||
"size": 604987,
|
||||
"gzip": 137591
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 279046,
|
||||
"gzip": 53062
|
||||
"size": 290412,
|
||||
"gzip": 54502
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 41697,
|
||||
"gzip": 11964
|
||||
"size": 41803,
|
||||
"gzip": 12011
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
|
@ -102,8 +102,8 @@
|
|||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 36434,
|
||||
"gzip": 10505
|
||||
"size": 36540,
|
||||
"gzip": 10554
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
|
@ -116,8 +116,8 @@
|
|||
"filename": "ReactTestUtils-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 37155,
|
||||
"gzip": 10582
|
||||
"size": 37255,
|
||||
"gzip": 10630
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
|
@ -165,141 +165,141 @@
|
|||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 102991,
|
||||
"gzip": 26927
|
||||
"size": 103067,
|
||||
"gzip": 27041
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 15184,
|
||||
"gzip": 5856
|
||||
"size": 15133,
|
||||
"gzip": 5835
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 92035,
|
||||
"gzip": 24618
|
||||
"size": 92111,
|
||||
"gzip": 24739
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 14818,
|
||||
"gzip": 5705
|
||||
"size": 14771,
|
||||
"gzip": 5680
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 95165,
|
||||
"gzip": 24327
|
||||
"size": 95191,
|
||||
"gzip": 24410
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 33262,
|
||||
"gzip": 8299
|
||||
"size": 33064,
|
||||
"gzip": 8279
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 94003,
|
||||
"gzip": 25175
|
||||
"size": 94079,
|
||||
"gzip": 25295
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 15642,
|
||||
"gzip": 6010
|
||||
"size": 15595,
|
||||
"gzip": 5990
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 389869,
|
||||
"gzip": 86413
|
||||
"size": 399001,
|
||||
"gzip": 87190
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 86808,
|
||||
"gzip": 26944
|
||||
"size": 90690,
|
||||
"gzip": 27874
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 313942,
|
||||
"gzip": 67385
|
||||
"size": 323070,
|
||||
"gzip": 68147
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 50754,
|
||||
"gzip": 16005
|
||||
"size": 54355,
|
||||
"gzip": 16860
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 318024,
|
||||
"gzip": 66603
|
||||
"size": 328230,
|
||||
"gzip": 67375
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 157473,
|
||||
"gzip": 27225
|
||||
"size": 168749,
|
||||
"gzip": 28640
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 443941,
|
||||
"gzip": 97414
|
||||
"size": 454044,
|
||||
"gzip": 98203
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 209855,
|
||||
"gzip": 36492
|
||||
"size": 220436,
|
||||
"gzip": 37780
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 310910,
|
||||
"gzip": 66329
|
||||
"size": 320190,
|
||||
"gzip": 67115
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 49219,
|
||||
"gzip": 15315
|
||||
"size": 52870,
|
||||
"gzip": 16241
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 315000,
|
||||
"gzip": 65520
|
||||
"size": 325364,
|
||||
"gzip": 66316
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 21221,
|
||||
"gzip": 5193
|
||||
"size": 21475,
|
||||
"gzip": 5309
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
|
@ -312,43 +312,43 @@
|
|||
"filename": "ReactShallowRenderer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 20928,
|
||||
"gzip": 4566
|
||||
"size": 21120,
|
||||
"gzip": 4625
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 18777,
|
||||
"gzip": 5303
|
||||
"size": 19408,
|
||||
"gzip": 5482
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 6429,
|
||||
"gzip": 2573
|
||||
"size": 6643,
|
||||
"gzip": 2618
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 292377,
|
||||
"gzip": 61765
|
||||
"size": 301505,
|
||||
"gzip": 62567
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 42443,
|
||||
"gzip": 13358
|
||||
"size": 46055,
|
||||
"gzip": 14278
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 10934,
|
||||
"gzip": 3388
|
||||
"size": 11040,
|
||||
"gzip": 3435
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.production.min.js",
|
||||
|
@ -375,29 +375,29 @@
|
|||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 438218,
|
||||
"gzip": 96267
|
||||
"size": 438891,
|
||||
"gzip": 94687
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 201883,
|
||||
"gzip": 35448
|
||||
"size": 204481,
|
||||
"gzip": 35139
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 291949,
|
||||
"gzip": 61587
|
||||
"size": 300825,
|
||||
"gzip": 62279
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 41327,
|
||||
"gzip": 13133
|
||||
"size": 44927,
|
||||
"gzip": 14054
|
||||
},
|
||||
{
|
||||
"filename": "react-is.development.js",
|
||||
|
@ -431,15 +431,43 @@
|
|||
"filename": "simple-cache-provider.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "simple-cache-provider",
|
||||
"size": 5830,
|
||||
"gzip": 1904
|
||||
"size": 5759,
|
||||
"gzip": 1870
|
||||
},
|
||||
{
|
||||
"filename": "simple-cache-provider.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "simple-cache-provider",
|
||||
"size": 1313,
|
||||
"gzip": 665
|
||||
"size": 1295,
|
||||
"gzip": 656
|
||||
},
|
||||
{
|
||||
"filename": "create-component-with-subscriptions.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "create-component-with-subscriptions",
|
||||
"size": 9931,
|
||||
"gzip": 3067
|
||||
},
|
||||
{
|
||||
"filename": "create-component-with-subscriptions.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "create-component-with-subscriptions",
|
||||
"size": 3783,
|
||||
"gzip": 1637
|
||||
},
|
||||
{
|
||||
"filename": "create-subscription.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "create-subscription",
|
||||
"size": 5491,
|
||||
"gzip": 1896
|
||||
},
|
||||
{
|
||||
"filename": "create-subscription.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "create-subscription",
|
||||
"size": 2190,
|
||||
"gzip": 1007
|
||||
}
|
||||
]
|
||||
}
|
10
yarn.lock
10
yarn.lock
|
@ -4897,6 +4897,12 @@ rx-lite@*, rx-lite@^4.0.8:
|
|||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
|
||||
rxjs@^5.5.6:
|
||||
version "5.5.6"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
|
||||
dependencies:
|
||||
symbol-observable "1.0.1"
|
||||
|
||||
safe-buffer@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
|
||||
|
@ -5217,6 +5223,10 @@ supports-hyperlinks@^1.0.1:
|
|||
has-flag "^2.0.0"
|
||||
supports-color "^5.0.0"
|
||||
|
||||
symbol-observable@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
|
||||
|
||||
symbol-tree@^3.2.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
|
||||
|
|
Loading…
Reference in New Issue