Compare commits

...

13 Commits

Author SHA1 Message Date
Andrew Clark
e0e8562e5a Update bundle sizes for 16.6.0-alpha.400d197 release 2018-10-04 17:54:44 -07:00
Andrew Clark
ba5a037fd1 Update error codes for 16.6.0-alpha.400d197 release 2018-10-04 17:54:44 -07:00
Andrew Clark
419b2be8ed Updating dependencies for react-noop-renderer 2018-10-04 17:47:29 -07:00
Andrew Clark
c97b29f12a Updating package versions for release 16.6.0-alpha.400d197 2018-10-04 17:47:29 -07:00
Sophie Alpert
400d197564 [private] Make sure deletions don't stop passive effects
Before the fix, the passive effect in the test is never executed.

We were previously waiting until the next commit phase to run effects. Now, we run them before scheduling work.
2018-10-04 16:15:45 -07:00
Alex Taylor
7177f831d7 [private] Add support for hooks to ReactDOMServer
Co-authored-by: Alex Taylor <alexmckenley@gmail.com>
Co-authored-by: Andrew Clark <acdlite@fb.com>
2018-10-04 16:15:45 -07:00
Sophie Alpert
13cd5a2d20 [private] Disallow useContext in class components 2018-10-04 16:15:45 -07:00
Andrew Clark
2888e06af6 [private] Defer useEffect until after paint
Effects scheduled by useEffect should not fire until after the browser
has had a chance to paint. However, they should be fired before any
subsequent mutations.

Also adds useMutationEffect and useLayoutEffect. useMutationEffect fires
during the host update phase. useLayoutEffect fires during the post-
update phase (the same phase as componentDidMount
and componentDidUpdate).
2018-10-04 16:13:12 -07:00
Andrew Clark
cb3471d556 [private] Put hooks behind feature flag 2018-10-04 16:13:12 -07:00
Andrew Clark
c7a56edd32 [private] Initial hooks implementation
Includes:
- useState
- useContext
- useEffect
- useRef
- useReducer
- useCallback
- useMemo
- useAPI
2018-10-04 15:17:55 -07:00
Sophie Alpert
31584c9e72 [private] Fix syntax error 2018-10-04 15:17:55 -07:00
Sophie Alpert
a2a8113222 [private] Disable failing CircleCI steps 2018-10-04 15:17:55 -07:00
Sophie Alpert
f92286b833 [private] Disable parallelism on Circle 2018-10-04 15:17:55 -07:00
43 changed files with 4363 additions and 351 deletions

View File

@@ -9,7 +9,7 @@ jobs:
TZ: /usr/share/zoneinfo/America/Los_Angeles
TRAVIS_REPO_SLUG: facebook/react
parallelism: 4
parallelism: 1
steps:
- checkout

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"workspaces": [
"packages/*"
],

View File

@@ -1,7 +1,7 @@
{
"name": "create-subscription",
"description": "utility for subscribing to external data sources inside React components",
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"repository": "facebook/react",
"files": [
"LICENSE",
@@ -10,7 +10,7 @@
"cjs/"
],
"peerDependencies": {
"react": "^16.3.0"
"react": "^16.3.0 || 16.6.0-alpha.400d197"
},
"devDependencies": {
"rxjs": "^5.5.6"

View File

@@ -1,6 +1,6 @@
{
"name": "jest-react",
"version": "0.2.0",
"version": "0.3.0-alpha.400d197",
"description": "Jest matchers and utilities for testing React components.",
"main": "index.js",
"repository": "facebook/react",
@@ -16,7 +16,7 @@
"homepage": "https://reactjs.org/",
"peerDependencies": {
"jest": "^23.0.1",
"react": "^16.0.0",
"react": "^16.0.0 || 16.6.0-alpha.400d197",
"react-test-renderer": "^16.0.0"
},
"files": [

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"main": "index.js",
"repository": "facebook/react",
"keywords": [
@@ -23,10 +23,10 @@
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.9.0"
"scheduler": "^0.10.0-alpha.400d197"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-cache",
"description": "A basic cache for React applications",
"version": "2.0.0-alpha.0",
"version": "16.6.0-alpha.400d197",
"repository": "facebook/react",
"files": [
"LICENSE",
@@ -10,6 +10,6 @@
"cjs/"
],
"peerDependencies": {
"react": "^16.3.0-alpha.1"
"react": "^16.3.0-alpha.1 || 16.6.0-alpha.400d197"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": "facebook/react",
@@ -16,10 +16,10 @@
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.9.0"
"scheduler": "^0.10.0-alpha.400d197"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
},
"files": [
"LICENSE",

View File

@@ -0,0 +1,650 @@
/**
* 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
*/
/* eslint-disable no-func-assign */
'use strict';
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
let ReactFeatureFlags;
let ReactDOM;
let ReactDOMServer;
let useState;
let useReducer;
let useEffect;
let useContext;
let useCallback;
let useMemo;
let useRef;
let useAPI;
let useMutationEffect;
let useLayoutEffect;
let forwardRef;
let yieldedValues;
let yieldValue;
let clearYields;
function initModules() {
// Reset warning cache.
jest.resetModuleRegistry();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.enableHooks_DEPRECATED = true;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
useState = React.useState;
useReducer = React.useReducer;
useEffect = React.useEffect;
useContext = React.useContext;
useCallback = React.useCallback;
useMemo = React.useMemo;
useRef = React.useRef;
useAPI = React.useAPI;
useMutationEffect = React.useMutationEffect;
useLayoutEffect = React.useLayoutEffect;
forwardRef = React.forwardRef;
yieldedValues = [];
yieldValue = value => {
yieldedValues.push(value);
};
clearYields = () => {
const ret = yieldedValues;
yieldedValues = [];
return ret;
};
// Make them available to the helpers.
return {
ReactDOM,
ReactDOMServer,
};
}
const {
resetModules,
itRenders,
itThrowsWhenRendering,
serverRender,
} = ReactDOMServerIntegrationUtils(initModules);
describe('ReactDOMServerHooks', () => {
beforeEach(() => {
resetModules();
});
function Text(props) {
yieldValue(props.text);
return <span>{props.text}</span>;
}
describe('useState', () => {
itRenders('basic render', async render => {
function Counter(props) {
const [count] = useState(0);
return <span>Count: {count}</span>;
}
const domNode = await render(<Counter />);
expect(domNode.textContent).toEqual('Count: 0');
});
itRenders('lazy state initialization', async render => {
function Counter(props) {
const [count] = useState(() => {
return 0;
});
return <span>Count: {count}</span>;
}
const domNode = await render(<Counter />);
expect(domNode.textContent).toEqual('Count: 0');
});
it('does not trigger a re-renders when updater is invoked outside current render function', async () => {
function UpdateCount({setCount, count, children}) {
if (count < 3) {
setCount(c => c + 1);
}
return <span>{children}</span>;
}
function Counter() {
let [count, setCount] = useState(0);
return (
<div>
<UpdateCount setCount={setCount} count={count}>
Count: {count}
</UpdateCount>
</div>
);
}
const domNode = await serverRender(<Counter />);
expect(domNode.textContent).toEqual('Count: 0');
});
itThrowsWhenRendering(
'if used inside a class component',
async render => {
class Counter extends React.Component {
render() {
let [count] = useState(0);
return <Text text={count} />;
}
}
return render(<Counter />);
},
'Hooks can only be called inside the body of a functional component.',
);
itRenders('multiple times when an updater is called', async render => {
function Counter() {
let [count, setCount] = useState(0);
if (count < 12) {
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
}
return <Text text={'Count: ' + count} />;
}
const domNode = await render(<Counter />);
expect(domNode.textContent).toEqual('Count: 12');
});
itRenders('until there are no more new updates', async render => {
function Counter() {
let [count, setCount] = useState(0);
if (count < 3) {
setCount(count + 1);
}
return <span>Count: {count}</span>;
}
const domNode = await render(<Counter />);
expect(domNode.textContent).toEqual('Count: 3');
});
itThrowsWhenRendering(
'after too many iterations',
async render => {
function Counter() {
let [count, setCount] = useState(0);
setCount(count + 1);
return <span>{count}</span>;
}
return render(<Counter />);
},
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
});
describe('useReducer', () => {
itRenders('with initial state', async render => {
function reducer(state, action) {
return action === 'increment' ? state + 1 : state;
}
function Counter() {
let [count] = useReducer(reducer, 0);
yieldValue('Render: ' + count);
return <Text text={count} />;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual(['Render: 0', 0]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('0');
});
itRenders('lazy initialization with initialAction', async render => {
function reducer(state, action) {
return action === 'increment' ? state + 1 : state;
}
function Counter() {
let [count] = useReducer(reducer, 0, 'increment');
yieldValue('Render: ' + count);
return <Text text={count} />;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual(['Render: 1', 1]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('1');
});
itRenders(
'multiple times when updates happen during the render phase',
async render => {
function reducer(state, action) {
return action === 'increment' ? state + 1 : state;
}
function Counter() {
let [count, dispatch] = useReducer(reducer, 0);
if (count < 3) {
dispatch('increment');
}
yieldValue('Render: ' + count);
return <Text text={count} />;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual([
'Render: 0',
'Render: 1',
'Render: 2',
'Render: 3',
3,
]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('3');
},
);
itRenders(
'using reducer passed at time of render, not time of dispatch',
async render => {
// This test is a bit contrived but it demonstrates a subtle edge case.
// Reducer A increments by 1. Reducer B increments by 10.
function reducerA(state, action) {
switch (action) {
case 'increment':
return state + 1;
case 'reset':
return 0;
}
}
function reducerB(state, action) {
switch (action) {
case 'increment':
return state + 10;
case 'reset':
return 0;
}
}
function Counter() {
let [reducer, setReducer] = useState(() => reducerA);
let [count, dispatch] = useReducer(reducer, 0);
if (count < 20) {
dispatch('increment');
// Swap reducers each time we increment
if (reducer === reducerA) {
setReducer(() => reducerB);
} else {
setReducer(() => reducerA);
}
}
yieldValue('Render: ' + count);
return <Text text={count} />;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual([
// The count should increase by alternating amounts of 10 and 1
// until we reach 21.
'Render: 0',
'Render: 10',
'Render: 11',
'Render: 21',
21,
]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('21');
},
);
});
describe('useMemo', () => {
itRenders('basic render', async render => {
function CapitalizedText(props) {
const text = props.text;
const capitalizedText = useMemo(
() => {
yieldValue(`Capitalize '${text}'`);
return text.toUpperCase();
},
[text],
);
return <Text text={capitalizedText} />;
}
const domNode = await render(<CapitalizedText text="hello" />);
expect(clearYields()).toEqual(["Capitalize 'hello'", 'HELLO']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('HELLO');
});
itRenders('if no inputs are provided', async render => {
function LazyCompute(props) {
const computed = useMemo(props.compute);
return <Text text={computed} />;
}
function computeA() {
yieldValue('compute A');
return 'A';
}
const domNode = await render(<LazyCompute compute={computeA} />);
expect(clearYields()).toEqual(['compute A', 'A']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('A');
});
itRenders(
'multiple times when updates happen during the render phase',
async render => {
function CapitalizedText(props) {
const [text, setText] = useState(props.text);
const capitalizedText = useMemo(
() => {
yieldValue(`Capitalize '${text}'`);
return text.toUpperCase();
},
[text],
);
if (text === 'hello') {
setText('hello, world.');
}
return <Text text={capitalizedText} />;
}
const domNode = await render(<CapitalizedText text="hello" />);
expect(clearYields()).toEqual([
"Capitalize 'hello'",
"Capitalize 'hello, world.'",
'HELLO, WORLD.',
]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('HELLO, WORLD.');
},
);
itRenders(
'should only invoke the memoized function when the inputs change',
async render => {
function CapitalizedText(props) {
const [text, setText] = useState(props.text);
const [count, setCount] = useState(0);
const capitalizedText = useMemo(
() => {
yieldValue(`Capitalize '${text}'`);
return text.toUpperCase();
},
[text],
);
yieldValue(count);
if (count < 3) {
setCount(count + 1);
}
if (text === 'hello' && count === 2) {
setText('hello, world.');
}
return <Text text={capitalizedText} />;
}
const domNode = await render(<CapitalizedText text="hello" />);
expect(clearYields()).toEqual([
"Capitalize 'hello'",
0,
1,
2,
// `capitalizedText` only recomputes when the text has changed
"Capitalize 'hello, world.'",
3,
'HELLO, WORLD.',
]);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('HELLO, WORLD.');
},
);
});
describe('useRef', () => {
itRenders('basic render', async render => {
function Counter(props) {
const count = useRef(0);
return <span>Count: {count.current}</span>;
}
const domNode = await render(<Counter />);
expect(domNode.textContent).toEqual('Count: 0');
});
itRenders(
'multiple times when updates happen during the render phase',
async render => {
function Counter(props) {
const [count, setCount] = useState(0);
const ref = useRef(count);
if (count < 3) {
const newCount = count + 1;
ref.current = newCount;
setCount(newCount);
}
yieldValue(count);
return <span>Count: {ref.current}</span>;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual([0, 1, 2, 3]);
expect(domNode.textContent).toEqual('Count: 3');
},
);
itRenders(
'always return the same reference through multiple renders',
async render => {
let firstRef = null;
function Counter(props) {
const [count, setCount] = useState(0);
const ref = useRef(count);
if (firstRef === null) {
firstRef = ref;
} else if (firstRef !== ref) {
throw new Error('should never change');
}
if (count < 3) {
setCount(count + 1);
} else {
firstRef = null;
}
yieldValue(count);
return <span>Count: {ref.current}</span>;
}
const domNode = await render(<Counter />);
expect(clearYields()).toEqual([0, 1, 2, 3]);
expect(domNode.textContent).toEqual('Count: 0');
},
);
});
describe('useEffect', () => {
itRenders('should ignore effects on the server', async render => {
function Counter(props) {
useEffect(() => {
yieldValue('should not be invoked');
});
return <Text text={'Count: ' + props.count} />;
}
const domNode = await render(<Counter count={0} />);
expect(clearYields()).toEqual(['Count: 0']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useCallback', () => {
itRenders('should ignore callbacks on the server', async render => {
function Counter(props) {
useCallback(() => {
yieldValue('should not be invoked');
});
return <Text text={'Count: ' + props.count} />;
}
const domNode = await render(<Counter count={0} />);
expect(clearYields()).toEqual(['Count: 0']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useAPI', () => {
it('should not be invoked on the server', async () => {
function Counter(props, ref) {
useAPI(ref, () => {
throw new Error('should not be invoked');
});
return <Text text={props.label + ': ' + ref.current} />;
}
Counter = forwardRef(Counter);
const counter = React.createRef();
counter.current = 0;
const domNode = await serverRender(
<Counter label="Count" ref={counter} />,
);
expect(clearYields()).toEqual(['Count: 0']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useMutationEffect', () => {
it('should warn when invoked during render', async () => {
function Counter() {
useMutationEffect(() => {
throw new Error('should not be invoked');
});
return <Text text="Count: 0" />;
}
const domNode = await serverRender(<Counter />, 1);
expect(clearYields()).toEqual(['Count: 0']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useLayoutEffect', () => {
it('should warn when invoked during render', async () => {
function Counter() {
useLayoutEffect(() => {
throw new Error('should not be invoked');
});
return <Text text="Count: 0" />;
}
const domNode = await serverRender(<Counter />, 1);
expect(clearYields()).toEqual(['Count: 0']);
expect(domNode.tagName).toEqual('SPAN');
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useContext', () => {
itRenders(
'can use the same context multiple times in the same function',
async render => {
const Context = React.createContext(
{foo: 0, bar: 0, baz: 0},
(a, b) => {
let result = 0;
if (a.foo !== b.foo) {
result |= 0b001;
}
if (a.bar !== b.bar) {
result |= 0b010;
}
if (a.baz !== b.baz) {
result |= 0b100;
}
return result;
},
);
function Provider(props) {
return (
<Context.Provider
value={{foo: props.foo, bar: props.bar, baz: props.baz}}>
{props.children}
</Context.Provider>
);
}
function FooAndBar() {
const {foo} = useContext(Context, 0b001);
const {bar} = useContext(Context, 0b010);
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
}
function Baz() {
const {baz} = useContext(Context, 0b100);
return <Text text={'Baz: ' + baz} />;
}
class Indirection extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
return this.props.children;
}
}
function App(props) {
return (
<div>
<Provider foo={props.foo} bar={props.bar} baz={props.baz}>
<Indirection>
<Indirection>
<FooAndBar />
</Indirection>
<Indirection>
<Baz />
</Indirection>
</Indirection>
</Provider>
</div>
);
}
const domNode = await render(<App foo={1} bar={3} baz={5} />);
expect(clearYields()).toEqual(['Foo: 1, Bar: 3', 'Baz: 5']);
expect(domNode.childNodes.length).toBe(2);
expect(domNode.firstChild.tagName).toEqual('SPAN');
expect(domNode.firstChild.textContent).toEqual('Foo: 1, Bar: 3');
expect(domNode.lastChild.tagName).toEqual('SPAN');
expect(domNode.lastChild.textContent).toEqual('Baz: 5');
},
);
});
});

View File

@@ -46,6 +46,11 @@ import {
createMarkupForRoot,
} from './DOMMarkupOperations';
import escapeTextForBrowser from './escapeTextForBrowser';
import {
prepareToUseHooks,
finishHooks,
Dispatcher,
} from './ReactPartialRendererHooks';
import {
Namespaces,
getIntrinsicNamespace,
@@ -68,6 +73,8 @@ type FlatReactChildren = Array<null | ReactNode>;
type toArrayType = (children: mixed) => FlatReactChildren;
const toArray = ((React.Children.toArray: any): toArrayType);
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
// This is only used in DEV.
// Each entry is `this.stack` from a currently executing renderer instance.
// (There may be more than one because ReactDOMServer is reentrant).
@@ -540,7 +547,11 @@ function resolve(
}
}
}
const componentIdentity = {};
prepareToUseHooks(componentIdentity);
inst = Component(element.props, publicContext, updater);
inst = finishHooks(Component, element.props, inst, publicContext);
if (inst == null || inst.render == null) {
child = inst;
validateRenderResult(child, Component);
@@ -786,6 +797,8 @@ class ReactDOMServerRenderer {
return null;
}
ReactCurrentOwner.currentDispatcher = Dispatcher;
let out = '';
while (out.length < bytes) {
if (this.stack.length === 0) {
@@ -828,6 +841,9 @@ class ReactDOMServerRenderer {
out += this.render(child, frame.context, frame.domNamespace);
}
}
ReactCurrentOwner.currentDispatcher = null;
return out;
}
@@ -945,9 +961,17 @@ class ReactDOMServerRenderer {
switch (elementType.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
const element: ReactElement = ((nextChild: any): ReactElement);
const nextChildren = toArray(
elementType.render(element.props, element.ref),
let nextChildren;
const componentIdentity = {};
prepareToUseHooks(componentIdentity);
nextChildren = elementType.render(element.props, element.ref);
nextChildren = finishHooks(
elementType.render,
element.props,
nextChildren,
element.ref,
);
nextChildren = toArray(nextChildren);
const frame: Frame = {
type: null,
domNamespace: parentNamespace,

View File

@@ -0,0 +1,365 @@
/**
* 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 type {ReactContext} from 'shared/ReactTypes';
import invariant from 'shared/invariant';
import warning from 'shared/warning';
type BasicStateAction<S> = S | (S => S);
type MaybeCallback<S> = void | null | (S => mixed);
type Dispatch<S, A> = (A, MaybeCallback<S>) => void;
type Update<S, A> = {
action: A,
next: Update<S, A> | null,
};
type UpdateQueue<S, A> = {
last: Update<S, A> | null,
dispatch: any,
};
type Hook = {
memoizedState: any,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
let currentlyRenderingComponent: Object | null = null;
let firstWorkInProgressHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
// Whether the work-in-progress hook is a re-rendered hook
let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<
UpdateQueue<any, any>,
Update<any, any>,
> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
function resolveCurrentlyRenderingComponent(): Object {
invariant(
currentlyRenderingComponent !== null,
'Hooks can only be called inside the body of a functional component.',
);
return currentlyRenderingComponent;
}
function createHook(): Hook {
return {
memoizedState: null,
queue: null,
next: null,
};
}
function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
isReRender = false;
firstWorkInProgressHook = workInProgressHook = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
isReRender = false;
// Append to the end of the list
workInProgressHook = workInProgressHook.next = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = workInProgressHook.next;
}
}
return workInProgressHook;
}
export function prepareToUseHooks(componentIdentity: Object): void {
currentlyRenderingComponent = componentIdentity;
// The following should have already been reset
// didScheduleRenderPhaseUpdate = false;
// firstWorkInProgressHook = null;
// numberOfReRenders = 0;
// renderPhaseUpdates = null;
// workInProgressHook = null;
}
export function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
// This must be called after every functional component to prevent hooks from
// being used in classes.
while (didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
// the `renderPhaseUpdates` map. Call the component again, reusing the
// work-in-progress hooks and applying the additional updates on top. Keep
// restarting until no more updates are scheduled.
didScheduleRenderPhaseUpdate = false;
numberOfReRenders += 1;
// Start over from the beginning of the list
workInProgressHook = null;
children = Component(props, refOrContext);
}
currentlyRenderingComponent = null;
firstWorkInProgressHook = null;
numberOfReRenders = 0;
renderPhaseUpdates = null;
workInProgressHook = null;
// These were reset above
// currentlyRenderingComponent = null;
// didScheduleRenderPhaseUpdate = false;
// firstWorkInProgressHook = null;
// numberOfReRenders = 0;
// renderPhaseUpdates = null;
// workInProgressHook = null;
return children;
}
function useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
return context._currentValue;
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
export function useState<S>(
initialState: S | (() => S),
): [S, Dispatch<S, BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
export function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
): [S, Dispatch<S, A>] {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<S, A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
} else {
if (reducer === basicStateReducer) {
// Special case for `useState`.
if (typeof initialState === 'function') {
initialState = initialState();
}
} else if (initialAction !== undefined && initialAction !== null) {
initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = initialState;
const queue: UpdateQueue<S, A> = (workInProgressHook.queue = {
last: null,
dispatch: null,
});
const dispatch: Dispatch<S, A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
}
}
function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
if (
workInProgressHook !== null &&
workInProgressHook.memoizedState !== null
) {
const prevState = workInProgressHook.memoizedState;
const prevInputs = prevState[1];
if (inputsAreEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate();
workInProgressHook.memoizedState = [nextValue, nextInputs];
return nextValue;
}
function useRef<T>(initialValue: T): {current: T} {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const previousRef = workInProgressHook.memoizedState;
if (previousRef === null) {
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
return ref;
} else {
return previousRef;
}
}
function useMutationEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
warning(
false,
'useMutationEffect does nothing on the server, because its effect cannot ' +
"be encoded into the server renderer's output format. This will lead " +
'to a mismatch between the initial, non-hydrated UI and the intended ' +
'UI. To avoid this, useMutationEffect should only be used in ' +
'components that render exclusively on the client.',
);
}
export function useLayoutEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
warning(
false,
'useLayoutEffect does nothing on the server, because its effect cannot ' +
"be encoded into the server renderer's output format. This will lead " +
'to a mismatch between the initial, non-hydrated UI and the intended ' +
'UI. To avoid this, useLayoutEffect should only be used in ' +
'components that render exclusively on the client.',
);
}
function dispatchAction<S, A>(
componentIdentity: Object,
queue: UpdateQueue<S, A>,
action: A,
) {
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
if (componentIdentity === currentlyRenderingComponent) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<S, A> = {
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
// This means an update has happened after the functional component has
// returned. On the server this is a no-op. In React Fiber, the update
// would be scheduled for a future render.
}
}
function inputsAreEqual(arr1, arr2) {
// Don't bother comparing lengths because these arrays are always
// passed inline.
for (let i = 0; i < arr1.length; i++) {
// Inlined Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
const val1 = arr1[i];
const val2 = arr2[i];
if (
(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
(val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
) {
continue;
}
return false;
}
return true;
}
function noop(): void {}
export const Dispatcher = {
useContext,
useMemo,
useReducer,
useRef,
useState,
useMutationEffect,
useLayoutEffect,
// useAPI is not run in the server environment
useAPI: noop,
// Callbacks are not run in the server environment.
useCallback: noop,
// Effects are not run in the server environment.
useEffect: noop,
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-is",
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": "facebook/react",

View File

@@ -6,9 +6,9 @@
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.9.0"
"scheduler": "^0.10.0-alpha.400d197"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
}
}

View File

@@ -10,10 +10,10 @@
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"regenerator-runtime": "^0.11.0",
"react-reconciler": "*"
"react-reconciler": "* || 0.16.0-alpha.400d197"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-reconciler",
"description": "React package for creating custom renderers.",
"version": "0.15.0",
"version": "0.16.0-alpha.400d197",
"keywords": [
"react"
],
@@ -22,13 +22,13 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
},
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.9.0"
"scheduler": "^0.10.0-alpha.400d197"
},
"browserify": {
"transform": [

View File

@@ -79,6 +79,7 @@ import {
prepareToReadContext,
calculateChangedBits,
} from './ReactFiberNewContext';
import {prepareToUseHooks, finishHooks, resetHooks} from './ReactFiberHooks';
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
import {
getMaskedContext,
@@ -124,7 +125,7 @@ export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
@@ -135,7 +136,7 @@ export function reconcileChildren(
workInProgress,
null,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
} else {
// If the current child is the same as the work in progress, it means that
@@ -148,7 +149,7 @@ export function reconcileChildren(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
}
}
@@ -157,7 +158,7 @@ function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
@@ -171,7 +172,7 @@ function forceUnmountCurrentAndReconcile(
workInProgress,
current.child,
null,
renderExpirationTime,
renderExpirationTime
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
@@ -181,34 +182,24 @@ function forceUnmountCurrentAndReconcile(
workInProgress,
null,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
}
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
type: any,
Component: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const render = type.render;
const render = Component.render;
const ref = workInProgress.ref;
if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else if (workInProgress.memoizedProps === nextProps) {
const currentRef = current !== null ? current.ref : null;
if (ref === currentRef) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// The rest is a fork of updateFunctionComponent
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
@@ -217,12 +208,15 @@ function updateForwardRef(
} else {
nextChildren = render(nextProps, ref);
}
nextChildren = finishHooks(render, nextProps, nextChildren, ref);
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
@@ -234,7 +228,7 @@ function updatePureComponent(
Component: any,
nextProps: any,
updateExpirationTime,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const render = Component.render;
@@ -251,7 +245,7 @@ function updatePureComponent(
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
}
}
@@ -274,7 +268,7 @@ function updatePureComponent(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
@@ -283,14 +277,14 @@ function updatePureComponent(
function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const nextChildren = workInProgress.pendingProps;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
@@ -299,14 +293,14 @@ function updateFragment(
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
@@ -315,7 +309,7 @@ function updateMode(
function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
@@ -326,7 +320,7 @@ function updateProfiler(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
@@ -348,13 +342,14 @@ function updateFunctionComponent(
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
renderExpirationTime
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
@@ -363,6 +358,7 @@ function updateFunctionComponent(
} else {
nextChildren = Component(nextProps, context);
}
nextChildren = finishHooks(Component, nextProps, nextChildren, context);
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
@@ -370,7 +366,7 @@ function updateFunctionComponent(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
@@ -381,7 +377,7 @@ function updateClassComponent(
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
@@ -403,13 +399,13 @@ function updateClassComponent(
workInProgress,
Component,
nextProps,
renderExpirationTime,
renderExpirationTime
);
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
renderExpirationTime
);
shouldUpdate = true;
} else {
@@ -418,7 +414,7 @@ function updateClassComponent(
workInProgress,
Component,
nextProps,
renderExpirationTime,
renderExpirationTime
);
}
} else {
@@ -427,7 +423,7 @@ function updateClassComponent(
workInProgress,
Component,
nextProps,
renderExpirationTime,
renderExpirationTime
);
}
return finishClassComponent(
@@ -436,7 +432,7 @@ function updateClassComponent(
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
renderExpirationTime
);
}
@@ -446,7 +442,7 @@ function finishClassComponent(
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
@@ -462,7 +458,7 @@ function finishClassComponent(
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
}
@@ -513,14 +509,14 @@ function finishClassComponent(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
} else {
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
}
@@ -543,7 +539,7 @@ function pushHostRootContext(workInProgress) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
root.pendingContext !== root.context
);
} else if (root.context) {
// Should always be set
@@ -559,7 +555,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
'file an issue.'
);
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
@@ -569,7 +565,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
updateQueue,
nextProps,
null,
renderExpirationTime,
renderExpirationTime
);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
@@ -582,7 +578,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
}
const root: FiberRoot = workInProgress.stateNode;
@@ -609,7 +605,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
workInProgress,
null,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
@@ -618,7 +614,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
resetHydrationState();
}
@@ -669,7 +665,7 @@ function updateHostComponent(current, workInProgress, renderExpirationTime) {
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
@@ -706,12 +702,12 @@ function mountIndeterminateComponent(
workInProgress,
Component,
updateExpirationTime,
renderExpirationTime,
renderExpirationTime
) {
invariant(
current === null,
'An indeterminate component should never have mounted. This error is ' +
'likely caused by a bug in React. Please file an issue.',
'likely caused by a bug in React. Please file an issue.'
);
const props = workInProgress.pendingProps;
@@ -723,7 +719,7 @@ function mountIndeterminateComponent(
Component = readLazyComponentType(Component);
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
workInProgress,
Component,
Component
));
const resolvedProps = resolveDefaultProps(Component, props);
let child;
@@ -734,7 +730,7 @@ function mountIndeterminateComponent(
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
renderExpirationTime
);
break;
}
@@ -744,7 +740,7 @@ function mountIndeterminateComponent(
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
renderExpirationTime
);
break;
}
@@ -754,7 +750,7 @@ function mountIndeterminateComponent(
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
renderExpirationTime
);
break;
}
@@ -765,7 +761,7 @@ function mountIndeterminateComponent(
Component,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
renderExpirationTime
);
break;
}
@@ -777,7 +773,7 @@ function mountIndeterminateComponent(
false,
'Element type is invalid. Received a promise that resolves to: %s. ' +
'Promise elements must resolve to a class or function.',
Component,
Component
);
}
}
@@ -789,6 +785,7 @@ function mountIndeterminateComponent(
const context = getMaskedContext(workInProgress, unmaskedContext);
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
let value;
@@ -805,7 +802,7 @@ function mountIndeterminateComponent(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
componentName
);
didWarnAboutBadClass[componentName] = true;
}
@@ -832,6 +829,9 @@ function mountIndeterminateComponent(
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// Throw out any hooks that were used.
resetHooks();
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
@@ -852,7 +852,7 @@ function mountIndeterminateComponent(
workInProgress,
Component,
getDerivedStateFromProps,
props,
props
);
}
@@ -864,17 +864,18 @@ function mountIndeterminateComponent(
Component,
true,
hasContext,
renderExpirationTime,
renderExpirationTime
);
} else {
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
value = finishHooks(Component, props, value, context);
if (__DEV__) {
if (Component) {
warningWithoutStack(
!Component.childContextTypes,
'%s(...): childContextTypes cannot be defined on a function component.',
Component.displayName || Component.name || 'Component',
Component.displayName || Component.name || 'Component'
);
}
if (workInProgress.ref !== null) {
@@ -895,7 +896,7 @@ function mountIndeterminateComponent(
false,
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail.%s',
info,
info
);
}
}
@@ -907,7 +908,7 @@ function mountIndeterminateComponent(
warningWithoutStack(
false,
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
componentName
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
@@ -923,7 +924,7 @@ function mountIndeterminateComponent(
warningWithoutStack(
false,
'%s: Function components do not support contextType.',
componentName,
componentName
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
@@ -938,7 +939,7 @@ function mountIndeterminateComponent(
function updatePlaceholderComponent(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
) {
if (enableSuspense) {
const nextProps = workInProgress.pendingProps;
@@ -990,14 +991,14 @@ function updatePlaceholderComponent(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
} else {
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
}
workInProgress.memoizedProps = nextProps;
@@ -1011,7 +1012,7 @@ function updatePlaceholderComponent(
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const nextChildren = workInProgress.pendingProps;
@@ -1025,7 +1026,7 @@ function updatePortalComponent(
workInProgress,
null,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextChildren);
} else {
@@ -1033,7 +1034,7 @@ function updatePortalComponent(
current,
workInProgress,
nextChildren,
renderExpirationTime,
renderExpirationTime
);
memoizeProps(workInProgress, nextChildren);
}
@@ -1043,7 +1044,7 @@ function updatePortalComponent(
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const providerType: ReactProviderType<any> = workInProgress.type;
const context: ReactContext<any> = providerType._context;
@@ -1063,7 +1064,7 @@ function updateContextProvider(
newProps,
'prop',
'Context.Provider',
ReactCurrentFiber.getCurrentFiberStackInDev,
ReactCurrentFiber.getCurrentFiberStackInDev
);
}
}
@@ -1082,7 +1083,7 @@ function updateContextProvider(
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
}
} else {
@@ -1092,7 +1093,7 @@ function updateContextProvider(
workInProgress,
context,
changedBits,
renderExpirationTime,
renderExpirationTime
);
}
}
@@ -1105,7 +1106,7 @@ function updateContextProvider(
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
) {
const context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
@@ -1117,7 +1118,7 @@ function updateContextConsumer(
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function. A context consumer expects a single child " +
'that is a function. If you did pass a function, make sure there ' +
'is no trailing or leading whitespace around it.',
'is no trailing or leading whitespace around it.'
);
}
@@ -1162,7 +1163,7 @@ function updateContextConsumer(
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
): Fiber | null {
cancelWorkTimer(workInProgress);
@@ -1208,7 +1209,7 @@ function memoizeState(workInProgress: Fiber, nextState: any) {
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
renderExpirationTime: ExpirationTime
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
@@ -1250,7 +1251,7 @@ function beginWork(
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
workInProgress.stateNode.containerInfo
);
break;
case ContextProvider: {
@@ -1267,7 +1268,7 @@ function beginWork(
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
}
}
@@ -1283,7 +1284,7 @@ function beginWork(
workInProgress,
Component,
updateExpirationTime,
renderExpirationTime,
renderExpirationTime
);
}
case FunctionComponent: {
@@ -1294,7 +1295,7 @@ function beginWork(
workInProgress,
Component,
unresolvedProps,
renderExpirationTime,
renderExpirationTime
);
}
case FunctionComponentLazy: {
@@ -1306,7 +1307,7 @@ function beginWork(
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
renderExpirationTime
);
workInProgress.memoizedProps = unresolvedProps;
return child;
@@ -1319,7 +1320,7 @@ function beginWork(
workInProgress,
Component,
unresolvedProps,
renderExpirationTime,
renderExpirationTime
);
}
case ClassComponentLazy: {
@@ -1331,7 +1332,7 @@ function beginWork(
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
renderExpirationTime
);
workInProgress.memoizedProps = unresolvedProps;
return child;
@@ -1346,13 +1347,13 @@ function beginWork(
return updatePlaceholderComponent(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
case ForwardRef: {
const type = workInProgress.type;
@@ -1361,7 +1362,7 @@ function beginWork(
workInProgress,
type,
workInProgress.pendingProps,
renderExpirationTime,
renderExpirationTime
);
}
case ForwardRefLazy: {
@@ -1373,7 +1374,7 @@ function beginWork(
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
renderExpirationTime
);
workInProgress.memoizedProps = unresolvedProps;
return child;
@@ -1388,13 +1389,13 @@ function beginWork(
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
renderExpirationTime
);
case PureComponent: {
const type = workInProgress.type;
@@ -1404,7 +1405,7 @@ function beginWork(
type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
renderExpirationTime
);
}
case PureComponentLazy: {
@@ -1417,7 +1418,7 @@ function beginWork(
Component,
resolveDefaultProps(Component, unresolvedProps),
updateExpirationTime,
renderExpirationTime,
renderExpirationTime
);
workInProgress.memoizedProps = unresolvedProps;
return child;
@@ -1426,7 +1427,7 @@ function beginWork(
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
'React. Please file an issue.'
);
}
}

View File

@@ -48,6 +48,7 @@ import {
requestCurrentTime,
computeExpirationForFiber,
scheduleWork,
flushPassiveEffectsBeforeSchedulingUpdateOnFiber,
} from './ReactFiberScheduler';
const fakeInternalInstance = {};
@@ -189,6 +190,7 @@ const classComponentUpdater = {
update.callback = callback;
}
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(fiber);
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
@@ -208,6 +210,7 @@ const classComponentUpdater = {
update.callback = callback;
}
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(fiber);
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
@@ -226,6 +229,7 @@ const classComponentUpdater = {
update.callback = callback;
}
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(fiber);
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},

View File

@@ -18,6 +18,7 @@ import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
import {
enableSchedulerTracing,
@@ -25,6 +26,10 @@ import {
enableSuspense,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
FunctionComponentLazy,
ForwardRef,
ForwardRefLazy,
ClassComponent,
ClassComponentLazy,
HostRoot,
@@ -76,10 +81,21 @@ import {
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
flushPassiveEffectsBeforeSchedulingUpdateOnFiber,
requestCurrentTime,
scheduleWork,
} from './ReactFiberScheduler';
import {StrictMode} from './ReactTypeOfMode';
import {
NoEffect as NoHookEffect,
UnmountSnapshot,
UnmountMutation,
MountMutation,
UnmountLayout,
MountLayout,
UnmountPassive,
MountPassive,
} from './ReactHookEffectTags';
const emptyObject = {};
@@ -179,11 +195,34 @@ function safelyDetachRef(current: Fiber) {
}
}
function safelyCallDestroy(current, destroy) {
if (__DEV__) {
invokeGuardedCallback(null, destroy, null);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(current, error);
}
} else {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, error);
}
}
}
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case FunctionComponentLazy:
case ForwardRef:
case ForwardRefLazy: {
commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
return;
}
case ClassComponent:
case ClassComponentLazy: {
if (finishedWork.effectTag & Snapshot) {
@@ -234,6 +273,41 @@ function commitBeforeMutationLifeCycles(
}
}
function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = null;
if (destroy !== null) {
destroy();
}
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
const destroy = create();
effect.destroy = typeof destroy === 'function' ? destroy : null;
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
export function commitPassiveHookEffects(finishedWork: Fiber): void {
commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
}
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
@@ -241,6 +315,28 @@ function commitLifeCycles(
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case FunctionComponentLazy:
case ForwardRef:
case ForwardRefLazy: {
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
const newUpdateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
if (newUpdateQueue !== null) {
const callbackList = newUpdateQueue.callbackList;
if (callbackList !== null) {
newUpdateQueue.callbackList = null;
for (let i = 0; i < callbackList.length; i++) {
const update = callbackList[i];
// Assume this is non-null, since otherwise it would not be part
// of the callback list.
const callback: () => mixed = (update.callback: any);
update.callback = null;
callback();
}
}
}
break;
}
case ClassComponent:
case ClassComponentLazy: {
const instance = finishedWork.stateNode;
@@ -361,6 +457,7 @@ function commitLifeCycles(
// entire queue. Any non-null value works.
// $FlowFixMe - Intentionally using a value other than an UpdateQueue.
finishedWork.updateQueue = emptyObject;
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(finishedWork);
scheduleWork(finishedWork, Sync);
} else {
// In strict mode, the Update effect is used to record the time at
@@ -431,6 +528,27 @@ function commitUnmount(current: Fiber): void {
onCommitUnmount(current);
switch (current.tag) {
case FunctionComponent:
case FunctionComponentLazy:
case ForwardRef:
case ForwardRefLazy: {
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const destroy = effect.destroy;
if (destroy !== null) {
safelyCallDestroy(current, destroy);
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
break;
}
case ClassComponent:
case ClassComponentLazy: {
safelyDetachRef(current);
@@ -806,11 +924,28 @@ function commitDeletion(current: Fiber): void {
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
if (!supportsMutation) {
switch (finishedWork.tag) {
case FunctionComponent:
case FunctionComponentLazy:
case ForwardRef:
case ForwardRefLazy: {
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
}
commitContainer(finishedWork);
return;
}
switch (finishedWork.tag) {
case FunctionComponent:
case FunctionComponentLazy:
case ForwardRef:
case ForwardRefLazy: {
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
case ClassComponent:
case ClassComponentLazy: {
return;

View File

@@ -8,7 +8,29 @@
*/
import {readContext} from './ReactFiberNewContext';
import {
useAPI,
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useMutationEffect,
useReducer,
useRef,
useState,
} from './ReactFiberHooks';
export const Dispatcher = {
readContext,
useAPI,
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useMutationEffect,
useReducer,
useRef,
useState,
};

View File

@@ -0,0 +1,764 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root direcreatey of this source tree.
*
* @flow
*/
import type {ReactContext} from 'shared/ReactTypes';
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {HookEffectTag} from './ReactHookEffectTags';
import {NoWork} from './ReactFiberExpirationTime';
import {readContext} from './ReactFiberNewContext';
import {
Snapshot as SnapshotEffect,
Update as UpdateEffect,
Callback as CallbackEffect,
Passive as PassiveEffect,
} from 'shared/ReactSideEffectTags';
import {
NoEffect as NoHookEffect,
UnmountSnapshot,
UnmountMutation,
MountMutation,
MountLayout,
UnmountPassive,
MountPassive,
} from './ReactHookEffectTags';
import {
scheduleWork,
computeExpirationForFiber,
flushPassiveEffectsBeforeSchedulingUpdateOnFiber,
requestCurrentTime,
} from './ReactFiberScheduler';
import invariant from 'shared/invariant';
type Update<S, A> = {
expirationTime: ExpirationTime,
action: A,
callback: null | (S => mixed),
next: Update<S, A> | null,
};
type UpdateQueue<S, A> = {
last: Update<S, A> | null,
dispatch: any,
};
type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
type Effect = {
tag: HookEffectTag,
create: () => mixed,
destroy: (() => mixed) | null,
inputs: Array<mixed>,
next: Effect,
};
export type FunctionComponentUpdateQueue = {
callbackList: Array<Update<any, any>> | null,
lastEffect: Effect | null,
};
type BasicStateAction<S> = S | (S => S);
type MaybeCallback<S> = void | null | (S => mixed);
type Dispatch<S, A> = (A, MaybeCallback<S>) => void;
// These are set right before calling the component.
let renderExpirationTime: ExpirationTime = NoWork;
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber | null = null;
// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
let firstCurrentHook: Hook | null = null;
let currentHook: Hook | null = null;
let firstWorkInProgressHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
let remainingExpirationTime: ExpirationTime = NoWork;
let componentUpdateQueue: FunctionComponentUpdateQueue | null = null;
// Updates scheduled during render will trigger an immediate re-render at the
// end of the current pass. We can't store these updates on the normal queue,
// because if the work is aborted, they should be discarded. Because this is
// a relatively rare case, we also don't want to add an additional field to
// either the hook or queue object types. So we store them in a lazily create
// map of queue -> render-phase updates, which are discarded once the component
// completes without re-rendering.
// Whether the work-in-progress hook is a re-rendered hook
let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<
UpdateQueue<any, any>,
Update<any, any>,
> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
function resolveCurrentlyRenderingFiber(): Fiber {
invariant(
currentlyRenderingFiber !== null,
'Hooks can only be called inside the body of a functional component.',
);
return currentlyRenderingFiber;
}
export function prepareToUseHooks(
current: Fiber | null,
workInProgress: Fiber,
nextRenderExpirationTime: ExpirationTime,
): void {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
firstCurrentHook = current !== null ? current.memoizedState : null;
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// remainingExpirationTime = NoWork;
// componentUpdateQueue = null;
// isReRender = false;
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
}
export function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
// This must be called after every functional component to prevent hooks from
// being used in classes.
while (didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
// the `renderPhaseUpdates` map. Call the component again, reusing the
// work-in-progress hooks and applying the additional updates on top. Keep
// restarting until no more updates are scheduled.
didScheduleRenderPhaseUpdate = false;
numberOfReRenders += 1;
// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;
componentUpdateQueue = null;
children = Component(props, refOrContext);
}
renderPhaseUpdates = null;
numberOfReRenders = 0;
const renderedWork: Fiber = (currentlyRenderingFiber: any);
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
if (componentUpdateQueue !== null) {
renderedWork.updateQueue = (componentUpdateQueue: any);
}
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
firstCurrentHook = null;
currentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
// Always set during createWorkInProgress
// isReRender = false;
// These were reset above
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
invariant(
!didRenderTooFewHooks,
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);
return children;
}
export function resetHooks(): void {
// This is called instead of `finishHooks` if the component throws. It's also
// called inside mountIndeterminateComponent if we determine the component
// is a module-style component.
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
firstCurrentHook = null;
currentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
// Always set during createWorkInProgress
// isReRender = false;
didScheduleRenderPhaseUpdate = false;
renderPhaseUpdates = null;
numberOfReRenders = 0;
}
function createHook(): Hook {
return {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
}
function cloneHook(hook: Hook): Hook {
return {
memoizedState: hook.memoizedState,
baseState: hook.memoizedState,
queue: hook.queue,
baseUpdate: hook.baseUpdate,
next: null,
};
}
function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
isReRender = false;
currentHook = firstCurrentHook;
if (currentHook === null) {
// This is a newly mounted hook
workInProgressHook = createHook();
} else {
// Clone the current hook.
workInProgressHook = cloneHook(currentHook);
}
firstWorkInProgressHook = workInProgressHook;
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
currentHook = firstCurrentHook;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
isReRender = false;
let hook;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
currentHook = currentHook.next;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
// Clone the current hook.
hook = cloneHook(currentHook);
}
}
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = workInProgressHook.next;
currentHook = currentHook !== null ? currentHook.next : null;
}
}
return workInProgressHook;
}
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
callbackList: null,
lastEffect: null,
};
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
export function useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
// Ensure we're in a functional component (class components support only the
// .unstable_read() form)
resolveCurrentlyRenderingFiber();
return readContext(context, observedBits);
}
export function useState<S>(
initialState: S | (() => S),
): [S, Dispatch<S, BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
export function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
): [S, Dispatch<S, A>] {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let queue: UpdateQueue<S, A> | null = (workInProgressHook.queue: any);
if (queue !== null) {
// Already have a queue, so this is an update.
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
newState = reducer(newState, action);
const callback = update.callback;
if (callback !== null) {
pushCallback(currentlyRenderingFiber, update);
}
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
// Don't persist the state accumlated from the render phase updates to
// the base state unless the queue is empty.
// TODO: Not sure if this is the desired semantics, but it's what we
// do for gDSFP. I can't remember why.
if (workInProgressHook.baseUpdate === queue.last) {
workInProgressHook.baseState = newState;
}
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
}
// The last update in the entire queue
const last = queue.last;
// The last update that is part of the base state.
const baseUpdate = workInProgressHook.baseUpdate;
// Find the first unprocessed update.
let first;
if (baseUpdate !== null) {
if (last !== null) {
// For the first update, the queue is a circular linked list where
// `queue.last.next = queue.first`. Once the first update commits, and
// the `baseUpdate` is no longer empty, we can unravel the list.
last.next = null;
}
first = baseUpdate.next;
} else {
first = last !== null ? last.next : null;
}
if (first !== null) {
let newState = workInProgressHook.baseState;
let newBaseState = null;
let newBaseUpdate = null;
let prevUpdate = baseUpdate;
let update = first;
let didSkip = false;
do {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime > renderExpirationTime) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
if (!didSkip) {
didSkip = true;
newBaseUpdate = prevUpdate;
newBaseState = newState;
}
// Update the remaining priority in the queue.
if (
remainingExpirationTime === NoWork ||
updateExpirationTime < remainingExpirationTime
) {
remainingExpirationTime = updateExpirationTime;
}
} else {
// Process this update.
const action = update.action;
newState = reducer(newState, action);
const callback = update.callback;
if (callback !== null) {
pushCallback(currentlyRenderingFiber, update);
}
}
prevUpdate = update;
update = update.next;
} while (update !== null && update !== first);
if (!didSkip) {
newBaseUpdate = prevUpdate;
newBaseState = newState;
}
workInProgressHook.memoizedState = newState;
workInProgressHook.baseUpdate = newBaseUpdate;
workInProgressHook.baseState = newBaseState;
}
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
return [workInProgressHook.memoizedState, dispatch];
}
// There's no existing queue, so this is the initial render.
if (reducer === basicStateReducer) {
// Special case for `useState`.
if (typeof initialState === 'function') {
initialState = initialState();
}
} else if (initialAction !== undefined && initialAction !== null) {
initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
queue = workInProgressHook.queue = {
last: null,
dispatch: null,
};
const dispatch: Dispatch<S, A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
}
function pushCallback(workInProgress: Fiber, update: Update<any, any>): void {
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.callbackList = [update];
} else {
const callbackList = componentUpdateQueue.callbackList;
if (callbackList === null) {
componentUpdateQueue.callbackList = [update];
} else {
callbackList.push(update);
}
}
workInProgress.effectTag |= CallbackEffect;
}
function pushEffect(tag, create, destroy, inputs) {
const effect: Effect = {
tag,
create,
destroy,
inputs,
// Circular
next: (null: any),
};
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
export function useRef<T>(initialValue: T): {current: T} {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let ref;
if (workInProgressHook.memoizedState === null) {
ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
} else {
ref = workInProgressHook.memoizedState;
}
return ref;
}
export function useMutationEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(
SnapshotEffect | UpdateEffect,
UnmountSnapshot | MountMutation,
create,
inputs,
);
}
export function useLayoutEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, inputs);
}
export function useEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
inputs,
);
}
function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let nextEffect;
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
const prevInputs = prevEffect.inputs;
nextEffect = pushEffect(
inputsAreEqual(nextInputs, prevInputs) ? NoHookEffect : hookEffectTag,
create,
prevEffect.destroy,
nextInputs,
);
} else {
nextEffect = pushEffect(hookEffectTag, create, null, nextInputs);
}
workInProgressHook.memoizedState = nextEffect;
currentlyRenderingFiber.effectTag |= fiberEffectTag;
}
export function useAPI<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array<mixed> | void | null,
): void {
// TODO: If inputs are provided, should we skip comparing the ref itself?
const nextInputs =
inputs !== null && inputs !== undefined
? inputs.concat([ref])
: [ref, create];
// TODO: I've implemented this on top of useEffect because it's almost the
// same thing, and it would require an equal amount of code. It doesn't seem
// like a common enough use case to justify the additional size.
useEffectImpl(
UpdateEffect,
UnmountMutation | MountLayout,
() => {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => refCallback(null);
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
},
nextInputs,
);
}
export function useCallback<T>(
callback: T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [callback];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (inputsAreEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
workInProgressHook.memoizedState = [callback, nextInputs];
return callback;
}
export function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (inputsAreEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate();
workInProgressHook.memoizedState = [nextValue, nextInputs];
return nextValue;
}
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
callback: void | null | (S => mixed),
) {
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<S, A> = {
expirationTime: renderExpirationTime,
action,
callback: callback !== undefined ? callback : null,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update: Update<S, A> = {
expirationTime,
action,
callback: callback !== undefined ? callback : null,
next: null,
};
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(fiber);
// Append the update to the end of the list.
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// Still circular.
update.next = first;
}
last.next = update;
}
queue.last = update;
scheduleWork(fiber, expirationTime);
}
}
function inputsAreEqual(arr1, arr2) {
// Don't bother comparing lengths because these arrays are always
// passed inline.
for (let i = 0; i < arr1.length; i++) {
// Inlined Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
const val1 = arr1[i];
const val2 = arr2[i];
if (
(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
(val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
) {
continue;
}
return false;
}
return true;
}

View File

@@ -57,6 +57,7 @@ import {
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
flushPassiveEffectsBeforeSchedulingUpdateOnFiber,
} from './ReactFiberScheduler';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
@@ -151,9 +152,11 @@ function scheduleRootUpdate(
);
update.callback = callback;
}
enqueueUpdate(current, update);
flushPassiveEffectsBeforeSchedulingUpdateOnFiber(current);
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}

View File

@@ -12,7 +12,15 @@ import type {Batch, FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {Interaction} from 'scheduler/src/Tracing';
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
import {
__interactionsRef,
__subscriberRef,
unstable_wrap as Schedule_tracing_wrap,
} from 'scheduler/tracing';
import {
unstable_scheduleCallback as Schedule_scheduleCallback,
unstable_cancelCallback as Schedule_cancelCallback,
} from 'scheduler';
import {
invokeGuardedCallback,
hasCaughtError,
@@ -34,6 +42,7 @@ import {
Ref,
Incomplete,
HostEffectMask,
Passive,
} from 'shared/ReactSideEffectTags';
import {
HostRoot,
@@ -120,6 +129,7 @@ import {
popContext as popLegacyContext,
} from './ReactFiberContext';
import {popProvider, resetContextDependences} from './ReactFiberNewContext';
import {resetHooks} from './ReactFiberHooks';
import {popHostContext, popHostContainer} from './ReactFiberHostContext';
import {
recordCommitTime,
@@ -148,6 +158,7 @@ import {
commitLifeCycles,
commitAttachRef,
commitDetachRef,
commitPassiveHookEffects,
} from './ReactFiberCommitWork';
import {Dispatcher} from './ReactFiberDispatcher';
@@ -251,6 +262,9 @@ let nextRenderDidError: boolean = false;
let nextEffect: Fiber | null = null;
let isCommitting: boolean = false;
let rootWithPendingPassiveEffects: FiberRoot | null = null;
let firstPassiveEffect: Fiber | null = null;
let passiveEffectCallbackHandle: * = null;
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
@@ -457,8 +471,6 @@ function commitBeforeMutationLifecycles() {
commitBeforeMutationLifeCycles(current, nextEffect);
}
// Don't cleanup effects yet;
// This will be done by commitAllLifeCycles()
nextEffect = nextEffect.nextEffect;
}
@@ -498,15 +510,54 @@ function commitAllLifeCycles(
commitAttachRef(nextEffect);
}
const next = nextEffect.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
// I'm not actually sure this matters because we can't reset firstEffect
// and lastEffect since they're on every node, not just the effectful
// ones. So we have to clean everything as we reuse nodes anyway.
nextEffect.nextEffect = null;
// Ensure that we reset the effectTag here so that we can rely on effect
// tags to reason about the current life-cycle.
nextEffect = next;
if (effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}
nextEffect = nextEffect.nextEffect;
}
}
function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
rootWithPendingPassiveEffects = null;
passiveEffectCallbackHandle = null;
// Set this to true to prevent re-entrancy
const previousIsRendering = isRendering;
isRendering = true;
let effect = firstEffect;
do {
if (effect.effectTag & Passive) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
commitPassiveHookEffects(effect);
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
captureCommitPhaseError(effect, error);
}
}
effect = effect.nextEffect;
} while (effect !== null);
isRendering = previousIsRendering;
// Check if work was scheduled by one of the effects
const rootExpirationTime = root.expirationTime;
if (rootExpirationTime !== NoWork) {
requestWork(root, rootExpirationTime);
}
}
@@ -525,6 +576,41 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
}
}
function flushPassiveEffectsBeforeSchedulingUpdateOnFiber(fiber: Fiber) {
if (rootWithPendingPassiveEffects !== null) {
// TODO: This is an unfortunate extra loop. We end up traversing to the root
// again in scheduleWorkToRoot. But we have to do this one first because it
// needs to happen before adding an update to the queue, and
// scheduleWorkToRoot may perform a synchronous re-render. Maybe we can
// solve this with batchedUpdates, or with the equivalent in the Scheduler
// package.
let node = fiber;
do {
switch (node.tag) {
case HostRoot: {
const root: FiberRoot = node.stateNode;
flushPassiveEffects(root);
return;
}
}
node = node.return;
} while (node !== null);
}
}
function flushPassiveEffects(root: FiberRoot) {
if (root === rootWithPendingPassiveEffects) {
rootWithPendingPassiveEffects = null;
if (passiveEffectCallbackHandle !== null) {
Schedule_cancelCallback(passiveEffectCallbackHandle);
passiveEffectCallbackHandle = null;
}
if (firstPassiveEffect !== null) {
commitPassiveEffects(root, firstPassiveEffect);
}
}
}
function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
isWorking = true;
isCommitting = true;
@@ -715,6 +801,33 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
}
}
if (firstEffect !== null && rootWithPendingPassiveEffects !== null) {
// This commit included a passive effect. These do not need to fire until
// after the next paint. Schedule an callback to fire them in an async
// event. To ensure serial execution, the callback will be flushed early if
// we enter rootWithPendingPassiveEffects commit phase before then.
const resolvedFirstEffect = (firstPassiveEffect = firstEffect);
let passiveEffectCallback;
if (enableSchedulerTracing) {
// TODO: Avoid this extra callback by mutating the tracing ref directly,
// like we do at the beginning of commitRoot. I've opted not to do that
// here because that code is still in flux.
passiveEffectCallback = Schedule_tracing_wrap(() => {
commitPassiveEffects(root, resolvedFirstEffect);
});
} else {
passiveEffectCallback = commitPassiveEffects.bind(
null,
root,
resolvedFirstEffect,
);
}
passiveEffectCallbackHandle = Schedule_scheduleCallback(
passiveEffectCallback,
);
}
isCommitting = false;
isWorking = false;
stopCommitLifeCyclesTimer();
@@ -1142,6 +1255,9 @@ function renderRoot(
'renderRoot was called recursively. This error is likely caused ' +
'by a bug in React. Please file an issue.',
);
flushPassiveEffects(root);
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;
@@ -1225,6 +1341,9 @@ function renderRoot(
try {
workLoop(isYieldy);
} catch (thrownValue) {
resetContextDependences();
resetHooks();
if (nextUnitOfWork === null) {
// This is a fatal error.
didFatal = true;
@@ -1287,6 +1406,7 @@ function renderRoot(
isWorking = false;
ReactCurrentOwner.currentDispatcher = null;
resetContextDependences();
resetHooks();
// Yield back to main thread.
if (didFatal) {
@@ -1416,16 +1536,8 @@ function renderRoot(
onComplete(root, rootWorkInProgress, expirationTime);
}
function dispatch(
sourceFiber: Fiber,
value: mixed,
expirationTime: ExpirationTime,
) {
invariant(
!isWorking || isCommitting,
'dispatch: Cannot dispatch during the render phase.',
);
function captureCommitPhaseError(sourceFiber: Fiber, value: mixed) {
const expirationTime = Sync;
let fiber = sourceFiber.return;
while (fiber !== null) {
switch (fiber.tag) {
@@ -1471,10 +1583,6 @@ function dispatch(
}
}
function captureCommitPhaseError(fiber: Fiber, error: mixed) {
return dispatch(fiber, error, Sync);
}
function computeThreadID(
expirationTime: ExpirationTime,
interactionThreadID: number,
@@ -2494,4 +2602,5 @@ export {
interactiveUpdates,
flushInteractiveUpdates,
computeUniqueAsyncExpiration,
flushPassiveEffectsBeforeSchedulingUpdateOnFiber,
};

View File

@@ -0,0 +1,19 @@
/**
* 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
*/
export type HookEffectTag = number;
export const NoEffect = /* */ 0b00000000;
export const UnmountSnapshot = /* */ 0b00000010;
export const UnmountMutation = /* */ 0b00000100;
export const MountMutation = /* */ 0b00001000;
export const UnmountLayout = /* */ 0b00010000;
export const MountLayout = /* */ 0b00100000;
export const MountPassive = /* */ 0b01000000;
export const UnmountPassive = /* */ 0b10000000;

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
let React = require('react');
let useContext;
let ReactNoop;
let gen;
@@ -20,7 +21,9 @@ describe('ReactNewContext', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.enableHooks_DEPRECATED = true;
React = require('react');
useContext = React.useContext;
ReactNoop = require('react-noop-renderer');
gen = require('random-seed');
});
@@ -43,17 +46,17 @@ describe('ReactNewContext', () => {
// a suite of tests for a given context consumer implementation.
sharedContextTests('Context.Consumer', Context => Context.Consumer);
sharedContextTests(
'Context.unstable_read inside function component',
'useContext inside functional component',
Context =>
function Consumer(props) {
const observedBits = props.unstable_observedBits;
const contextValue = Context.unstable_read(observedBits);
const contextValue = useContext(Context, observedBits);
const render = props.children;
return render(contextValue);
},
);
sharedContextTests(
'Context.unstable_read inside class component',
'unstable_read inside class component',
Context =>
class Consumer extends React.Component {
render() {
@@ -1236,7 +1239,7 @@ describe('ReactNewContext', () => {
});
});
describe('unstable_readContext', () => {
describe('useContext', () => {
it('can use the same context multiple times in the same function', () => {
const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
let result = 0;
@@ -1262,13 +1265,13 @@ describe('ReactNewContext', () => {
}
function FooAndBar() {
const {foo} = Context.unstable_read(0b001);
const {bar} = Context.unstable_read(0b010);
const {foo} = useContext(Context, 0b001);
const {bar} = useContext(Context, 0b010);
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
}
function Baz() {
const {baz} = Context.unstable_read(0b100);
const {baz} = useContext(Context, 0b100);
return <Text text={'Baz: ' + baz} />;
}
@@ -1327,6 +1330,19 @@ describe('ReactNewContext', () => {
span('Baz: 2'),
]);
});
it('throws when used in a class component', () => {
const Context = React.createContext(0);
class Foo extends React.Component {
render() {
return useContext(Context);
}
}
ReactNoop.render(<Foo />);
expect(ReactNoop.flush).toThrow(
'Hooks can only be called inside the body of a functional component.',
);
});
});
it('unwinds after errors in complete phase', () => {

View File

@@ -1,6 +1,6 @@
{
"name": "react-test-renderer",
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"description": "React package for snapshot testing.",
"main": "index.js",
"repository": "facebook/react",
@@ -17,11 +17,11 @@
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"react-is": "^16.5.2",
"scheduler": "^0.9.0"
"react-is": "^16.6.0-alpha.400d197",
"scheduler": "^0.10.0-alpha.400d197"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.0.0 || 16.6.0-alpha.400d197"
},
"files": [
"LICENSE",

View File

@@ -4,7 +4,7 @@
"keywords": [
"react"
],
"version": "16.5.2",
"version": "16.6.0-alpha.400d197",
"homepage": "https://reactjs.org/",
"bugs": "https://github.com/facebook/react/issues",
"license": "MIT",
@@ -24,7 +24,7 @@
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.9.0"
"scheduler": "^0.10.0-alpha.400d197"
},
"browserify": {
"transform": [

View File

@@ -13,7 +13,7 @@ import {
REACT_STRICT_MODE_TYPE,
REACT_PLACEHOLDER_TYPE,
} from 'shared/ReactSymbols';
import {enableSuspense} from 'shared/ReactFeatureFlags';
import {enableSuspense, enableHooks_DEPRECATED} from 'shared/ReactFeatureFlags';
import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
@@ -28,6 +28,18 @@ import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import pure from './pure';
import {
useAPI,
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useMutationEffect,
useReducer,
useRef,
useState,
} from './ReactHooks';
import {
createElementWithValidation,
createFactoryWithValidation,
@@ -72,4 +84,17 @@ if (enableSuspense) {
React.lazy = lazy;
}
if (enableHooks_DEPRECATED) {
React.useAPI = useAPI;
React.useCallback = useCallback;
React.useContext = useContext;
React.useEffect = useEffect;
React.useLayoutEffect = useLayoutEffect;
React.useMemo = useMemo;
React.useMutationEffect = useMutationEffect;
React.useReducer = useReducer;
React.useRef = useRef;
React.useState = useState;
}
export default React;

View File

@@ -0,0 +1,98 @@
/**
* 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
*/
import type {ReactContext} from 'shared/ReactTypes';
import invariant from 'shared/invariant';
import ReactCurrentOwner from './ReactCurrentOwner';
function resolveDispatcher() {
const dispatcher = ReactCurrentOwner.currentDispatcher;
invariant(
dispatcher !== null,
'Hooks can only be called inside the body of a functional component.',
);
return dispatcher;
}
export function useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(context, observedBits);
}
export function useState<S>(initialState: S | (() => S)) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialState, initialAction);
}
export function useRef<T>(initialValue: T): {current: T} {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
export function useEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, inputs);
}
export function useMutationEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useMutationEffect(create, inputs);
}
export function useLayoutEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, inputs);
}
export function useCallback(
callback: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, inputs);
}
export function useMemo(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, inputs);
}
export function useAPI<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useAPI(ref, create, inputs);
}

View File

@@ -1,6 +1,6 @@
{
"name": "scheduler",
"version": "0.9.0",
"version": "0.10.0-alpha.400d197",
"description": "Cooperative scheduler for the browser environment.",
"main": "index.js",
"repository": "facebook/react",

View File

@@ -12,6 +12,7 @@ export const enableUserTimingAPI = __DEV__;
// Suspense
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
// Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
export const debugRenderPhaseSideEffects = false;

View File

@@ -10,25 +10,26 @@
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
export const NoEffect = /* */ 0b000000000000;
export const PerformedWork = /* */ 0b000000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010;
export const Update = /* */ 0b00000000100;
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
export const Placement = /* */ 0b000000000010;
export const Update = /* */ 0b000000000100;
export const PlacementAndUpdate = /* */ 0b000000000110;
export const Deletion = /* */ 0b000000001000;
export const ContentReset = /* */ 0b000000010000;
export const Callback = /* */ 0b000000100000;
export const DidCapture = /* */ 0b000001000000;
export const Ref = /* */ 0b000010000000;
export const Snapshot = /* */ 0b000100000000;
export const Passive = /* */ 0b001000000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b001110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b00111111111;
export const HostEffectMask = /* */ 0b001111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
export const Incomplete = /* */ 0b010000000000;
export const ShouldCapture = /* */ 0b100000000000;

View File

@@ -8,4 +8,4 @@
'use strict';
// TODO: this is special because it gets imported during build.
module.exports = '16.5.2';
module.exports = '16.6.0-alpha.400d197';

View File

@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;

View File

@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;

View File

@@ -15,6 +15,7 @@ import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native-fb';
// Re-export dynamic flags from the fbsource version.
export const {
enableSuspense,
enableHooks_DEPRECATED,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
warnAboutDeprecatedLifecycles,

View File

@@ -15,6 +15,7 @@ import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native-oss';
export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
export const enableUserTimingAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = false;

View File

@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;

View File

@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableSuspense = false;
export const enableHooks_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;

View File

@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableSuspense = true;
export const enableHooks_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;

View File

@@ -21,6 +21,9 @@ export const {
disableInputAttributeSyncing,
} = require('ReactFeatureFlags');
// The rest of the flags are static for better dead code elimination.
export const enableHooks_DEPRECATED = false;
// In www, we have experimental support for gathering data
// from User Timing API calls in production. By default, we
// only emit performance.mark/measure calls in __DEV__. But if

View File

@@ -14,7 +14,7 @@ if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
COMMANDS_TO_RUN+=('./scripts/circleci/check_license.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/check_modules.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/test_print_warnings.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/track_stats.sh')
# COMMANDS_TO_RUN+=('./scripts/circleci/track_stats.sh')
fi
if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
@@ -33,7 +33,8 @@ if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
fi
if [ $((3 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
COMMANDS_TO_RUN+=('./scripts/circleci/test_coverage.sh')
true
# COMMANDS_TO_RUN+=('./scripts/circleci/test_coverage.sh')
fi
RETURN_CODES=()

View File

@@ -287,5 +287,12 @@
"285": "The root failed to unmount after an error. This is likely a bug in React. Please file an issue.",
"286": "%s(...): the first argument must be a React class instance. Instead received: %s.",
"287": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `schedule/tracking` module with `schedule/tracking-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling",
"288": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `schedule/tracing` module with `schedule/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling"
"288": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `schedule/tracing` module with `schedule/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling",
"289": "Hooks can only be called inside the body of a functional component.",
"290": "Rendered fewer hooks than expected. This may be caused by an accidental early return statement.",
"291": "Too many re-renders. React limits the number of renders to prevent an infinite loop.",
"292": "Function components cannot have refs.",
"293": "Element ref was specified as a string (%s) but no owner was set. This could happen for one of the following reasons:\n1. You may be adding a ref to a function component\n2. You may be adding a ref to a component that was not created inside a component's render method\n3. You have multiple copies of React loaded\nSee https://fb.me/react-refs-must-have-owner for more information.",
"294": "Log of yielded values is not empty. Call expect(ReactTestRenderer).toHaveYielded(...) first.",
"295": "The matcher `toHaveYielded` expects an instance of React Test Renderer.\n\nTry: expect(ReactTestRenderer).toHaveYielded(expectedYields)"
}

View File

@@ -4,29 +4,29 @@
"filename": "react.development.js",
"bundleType": "UMD_DEV",
"packageName": "react",
"size": 93884,
"gzip": 24854
"size": 95856,
"gzip": 25185
},
{
"filename": "react.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react",
"size": 11927,
"gzip": 4698
"size": 11955,
"gzip": 4712
},
{
"filename": "react.development.js",
"bundleType": "NODE_DEV",
"packageName": "react",
"size": 57366,
"gzip": 15703
"size": 59338,
"gzip": 16044
},
{
"filename": "react.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react",
"size": 6245,
"gzip": 2651
"size": 6273,
"gzip": 2663
},
{
"filename": "React-dev.js",
@@ -46,29 +46,29 @@
"filename": "react-dom.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 674649,
"gzip": 157593
"size": 700012,
"gzip": 162934
},
{
"filename": "react-dom.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 95539,
"gzip": 31074
"size": 100906,
"gzip": 32927
},
{
"filename": "react-dom.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 669872,
"gzip": 156183
"size": 695271,
"gzip": 161517
},
{
"filename": "react-dom.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 95530,
"gzip": 30637
"size": 100981,
"gzip": 32458
},
{
"filename": "ReactDOM-dev.js",
@@ -88,29 +88,29 @@
"filename": "react-dom-test-utils.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 47057,
"gzip": 12756
"size": 47072,
"gzip": 12775
},
{
"filename": "react-dom-test-utils.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 10224,
"gzip": 3802
"size": 10238,
"gzip": 3814
},
{
"filename": "react-dom-test-utils.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 46771,
"gzip": 12693
"size": 46786,
"gzip": 12712
},
{
"filename": "react-dom-test-utils.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 9994,
"gzip": 3733
"size": 10008,
"gzip": 3747
},
{
"filename": "ReactTestUtils-dev.js",
@@ -123,29 +123,29 @@
"filename": "react-dom-unstable-native-dependencies.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 63044,
"gzip": 16436
"size": 63056,
"gzip": 16447
},
{
"filename": "react-dom-unstable-native-dependencies.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 11263,
"gzip": 3892
"size": 11277,
"gzip": 3906
},
{
"filename": "react-dom-unstable-native-dependencies.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 62708,
"gzip": 16304
"size": 62720,
"gzip": 16315
},
{
"filename": "react-dom-unstable-native-dependencies.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 10998,
"gzip": 3784
"size": 11012,
"gzip": 3797
},
{
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
@@ -165,29 +165,29 @@
"filename": "react-dom-server.browser.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 108670,
"gzip": 28897
"size": 118750,
"gzip": 31554
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 15730,
"gzip": 6014
"size": 17615,
"gzip": 6758
},
{
"filename": "react-dom-server.browser.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 104708,
"gzip": 27875
"size": 114788,
"gzip": 30595
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 15630,
"gzip": 5959
"size": 17515,
"gzip": 6749
},
{
"filename": "ReactDOMServer-dev.js",
@@ -207,43 +207,43 @@
"filename": "react-dom-server.node.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 106676,
"gzip": 28449
"size": 116756,
"gzip": 31142
},
{
"filename": "react-dom-server.node.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 16455,
"gzip": 6271
"size": 18340,
"gzip": 7057
},
{
"filename": "react-art.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-art",
"size": 459554,
"gzip": 102726
"size": 484902,
"gzip": 107969
},
{
"filename": "react-art.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-art",
"size": 87791,
"gzip": 26848
"size": 93158,
"gzip": 28714
},
{
"filename": "react-art.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-art",
"size": 389691,
"gzip": 85145
"size": 415090,
"gzip": 90504
},
{
"filename": "react-art.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-art",
"size": 51919,
"gzip": 15862
"size": 57356,
"gzip": 17749
},
{
"filename": "ReactART-dev.js",
@@ -291,29 +291,29 @@
"filename": "react-test-renderer.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-test-renderer",
"size": 402086,
"gzip": 87782
"size": 427925,
"gzip": 93238
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-test-renderer",
"size": 53165,
"gzip": 16146
"size": 58663,
"gzip": 18040
},
{
"filename": "react-test-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
"size": 397546,
"gzip": 86625
"size": 423099,
"gzip": 92023
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-test-renderer",
"size": 52878,
"gzip": 15949
"size": 58360,
"gzip": 17902
},
{
"filename": "ReactTestRenderer-dev.js",
@@ -326,29 +326,29 @@
"filename": "react-test-renderer-shallow.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-test-renderer",
"size": 26950,
"gzip": 7244
"size": 26964,
"gzip": 7257
},
{
"filename": "react-test-renderer-shallow.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-test-renderer",
"size": 7341,
"gzip": 2404
"size": 7355,
"gzip": 2416
},
{
"filename": "react-test-renderer-shallow.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
"size": 21318,
"gzip": 5797
"size": 21332,
"gzip": 5810
},
{
"filename": "react-test-renderer-shallow.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-test-renderer",
"size": 8087,
"gzip": 2676
"size": 8101,
"gzip": 2688
},
{
"filename": "ReactShallowRenderer-dev.js",
@@ -361,57 +361,57 @@
"filename": "react-noop-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-noop-renderer",
"size": 24425,
"gzip": 5503
"size": 24439,
"gzip": 5516
},
{
"filename": "react-noop-renderer.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
"size": 9125,
"gzip": 3170
"size": 9139,
"gzip": 3183
},
{
"filename": "react-reconciler.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 385420,
"gzip": 83190
"size": 410843,
"gzip": 88562
},
{
"filename": "react-reconciler.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
"size": 51602,
"gzip": 15398
"size": 57097,
"gzip": 17284
},
{
"filename": "react-reconciler-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 383913,
"gzip": 82571
"size": 409370,
"gzip": 87970
},
{
"filename": "react-reconciler-persistent.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
"size": 51613,
"gzip": 15404
"size": 57108,
"gzip": 17290
},
{
"filename": "react-reconciler-reflection.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 16483,
"gzip": 5003
"size": 16506,
"gzip": 5020
},
{
"filename": "react-reconciler-reflection.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
"size": 2614,
"gzip": 1154
"size": 2628,
"gzip": 1166
},
{
"filename": "react-call-return.development.js",
@@ -431,29 +431,29 @@
"filename": "react-is.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-is",
"size": 7477,
"gzip": 2349
"size": 7491,
"gzip": 2362
},
{
"filename": "react-is.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-is",
"size": 2093,
"gzip": 830
"size": 2107,
"gzip": 841
},
{
"filename": "react-is.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-is",
"size": 7288,
"gzip": 2296
"size": 7302,
"gzip": 2310
},
{
"filename": "react-is.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-is",
"size": 2054,
"gzip": 767
"size": 2068,
"gzip": 778
},
{
"filename": "ReactIs-dev.js",
@@ -487,57 +487,57 @@
"filename": "create-subscription.development.js",
"bundleType": "NODE_DEV",
"packageName": "create-subscription",
"size": 9518,
"gzip": 3106
"size": 9532,
"gzip": 3118
},
{
"filename": "create-subscription.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "create-subscription",
"size": 2886,
"gzip": 1347
"size": 2900,
"gzip": 1360
},
{
"filename": "React-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react",
"size": 53743,
"gzip": 14794
"size": 55769,
"gzip": 15177
},
{
"filename": "React-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react",
"size": 14506,
"gzip": 4037
"size": 14520,
"gzip": 4050
},
{
"filename": "ReactDOM-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 687555,
"gzip": 156915
"size": 713165,
"gzip": 162340
},
{
"filename": "ReactDOM-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
"size": 294160,
"gzip": 54060
"size": 314121,
"gzip": 57859
},
{
"filename": "ReactTestUtils-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 42463,
"gzip": 11444
"gzip": 11447
},
{
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 59004,
"gzip": 14969
"size": 59002,
"gzip": 14967
},
{
"filename": "ReactDOMUnstableNativeDependencies-prod.js",
@@ -550,92 +550,92 @@
"filename": "ReactDOMServer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 104509,
"gzip": 27284
"size": 114846,
"gzip": 30075
},
{
"filename": "ReactDOMServer-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
"size": 34456,
"gzip": 8277
"size": 40415,
"gzip": 9645
},
{
"filename": "ReactART-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-art",
"size": 393817,
"gzip": 83637
"size": 419427,
"gzip": 89093
},
{
"filename": "ReactART-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-art",
"size": 166983,
"gzip": 28058
"size": 185433,
"gzip": 31501
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
"size": 520658,
"gzip": 114873
"size": 546289,
"gzip": 120400
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 223402,
"gzip": 38719
"size": 242031,
"gzip": 42284
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
"size": 520345,
"gzip": 114792
"size": 545914,
"gzip": 120294
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
"size": 213455,
"gzip": 37150
"size": 231696,
"gzip": 40617
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
"size": 510566,
"gzip": 112380
"size": 536136,
"gzip": 117875
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 205689,
"gzip": 35638
"size": 224328,
"gzip": 39195
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
"size": 510602,
"gzip": 112396
"size": 536171,
"gzip": 117888
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
"size": 205725,
"gzip": 35653
"size": 224364,
"gzip": 39211
},
{
"filename": "ReactTestRenderer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-test-renderer",
"size": 401769,
"gzip": 85392
"size": 427500,
"gzip": 90842
},
{
"filename": "ReactShallowRenderer-dev.js",
@@ -676,15 +676,15 @@
"filename": "scheduler.development.js",
"bundleType": "NODE_DEV",
"packageName": "scheduler",
"size": 21862,
"gzip": 5892
"size": 21876,
"gzip": 5905
},
{
"filename": "scheduler.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "scheduler",
"size": 4876,
"gzip": 1892
"size": 4890,
"gzip": 1904
},
{
"filename": "SimpleCacheProvider-dev.js",
@@ -704,36 +704,36 @@
"filename": "react-noop-renderer-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-noop-renderer",
"size": 24544,
"gzip": 5516
"size": 24558,
"gzip": 5529
},
{
"filename": "react-noop-renderer-persistent.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
"size": 9147,
"gzip": 3176
"size": 9161,
"gzip": 3188
},
{
"filename": "react-dom.profiling.min.js",
"bundleType": "NODE_PROFILING",
"packageName": "react-dom",
"size": 98599,
"gzip": 31327
"size": 104018,
"gzip": 33071
},
{
"filename": "ReactNativeRenderer-profiling.js",
"bundleType": "RN_OSS_PROFILING",
"packageName": "react-native-renderer",
"size": 221225,
"gzip": 38630
"size": 239331,
"gzip": 42163
},
{
"filename": "ReactFabric-profiling.js",
"bundleType": "RN_OSS_PROFILING",
"packageName": "react-native-renderer",
"size": 212938,
"gzip": 37136
"size": 231194,
"gzip": 40725
},
{
"filename": "Scheduler-dev.js",
@@ -760,64 +760,64 @@
"filename": "React-profiling.js",
"bundleType": "FB_WWW_PROFILING",
"packageName": "react",
"size": 14506,
"gzip": 4037
"size": 14520,
"gzip": 4050
},
{
"filename": "ReactDOM-profiling.js",
"bundleType": "FB_WWW_PROFILING",
"packageName": "react-dom",
"size": 301234,
"gzip": 55559
"size": 319337,
"gzip": 58986
},
{
"filename": "ReactNativeRenderer-profiling.js",
"bundleType": "RN_FB_PROFILING",
"packageName": "react-native-renderer",
"size": 229587,
"gzip": 40094
"size": 247678,
"gzip": 43597
},
{
"filename": "ReactFabric-profiling.js",
"bundleType": "RN_FB_PROFILING",
"packageName": "react-native-renderer",
"size": 212897,
"gzip": 37119
"size": 231153,
"gzip": 40708
},
{
"filename": "react.profiling.min.js",
"bundleType": "UMD_PROFILING",
"packageName": "react",
"size": 14134,
"gzip": 5226
"size": 14162,
"gzip": 5238
},
{
"filename": "react-dom.profiling.min.js",
"bundleType": "UMD_PROFILING",
"packageName": "react-dom",
"size": 98532,
"gzip": 31780
"size": 103887,
"gzip": 33587
},
{
"filename": "scheduler-tracing.development.js",
"bundleType": "NODE_DEV",
"packageName": "scheduler",
"size": 10400,
"gzip": 2366
"size": 10380,
"gzip": 2363
},
{
"filename": "scheduler-tracing.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "scheduler",
"size": 719,
"gzip": 374
"size": 733,
"gzip": 385
},
{
"filename": "scheduler-tracing.profiling.min.js",
"bundleType": "NODE_PROFILING",
"packageName": "scheduler",
"size": 3334,
"gzip": 991
"size": 3348,
"gzip": 1004
},
{
"filename": "SchedulerTracing-dev.js",
@@ -844,15 +844,15 @@
"filename": "react-cache.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-cache",
"size": 10418,
"gzip": 3192
"size": 10432,
"gzip": 3208
},
{
"filename": "react-cache.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-cache",
"size": 1662,
"gzip": 820
"size": 1676,
"gzip": 832
},
{
"filename": "ReactCache-dev.js",
@@ -867,6 +867,48 @@
"packageName": "react-cache",
"size": 3737,
"gzip": 1134
},
{
"filename": "react-cache.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-cache",
"size": 10639,
"gzip": 3260
},
{
"filename": "react-cache.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-cache",
"size": 1845,
"gzip": 912
},
{
"filename": "jest-react.development.js",
"bundleType": "NODE_DEV",
"packageName": "jest-react",
"size": 9085,
"gzip": 3059
},
{
"filename": "jest-react.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "jest-react",
"size": 3671,
"gzip": 1664
},
{
"filename": "JestReact-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "jest-react",
"size": 5910,
"gzip": 1816
},
{
"filename": "JestReact-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "jest-react",
"size": 5103,
"gzip": 1616
}
]
}