mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge branch '15-dev' into 15-stable
This commit is contained in:
commit
4b9d48a150
100 files changed
+4168
-1895
No files matched your search
+1
-1
@@ -34,4 +34,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
[version]
|
||||
^0.26.0
|
||||
^0.27.0
|
||||
+28
-2
@@ -1,14 +1,40 @@
|
||||
## 15.2.1 (July 8, 2016)
|
||||
|
||||
### React
|
||||
- Fix errant warning related to `shouldHaveDebugID`. ([@gaearon](https://github.com/gaearon) in [#7193](https://github.com/facebook/react/pull/7193))
|
||||
- Better removal of dev-only code, leading to a small reduction in the minified production bundle size. ([@gaearon](https://github.com/gaearon) in [#7188](https://github.com/facebook/react/pull/7188), [#7189](https://github.com/facebook/react/pull/7189))
|
||||
|
||||
### React DOM
|
||||
- Add stack trace to unknown property warning. ([@jimfb](https://github.com/jimfb) in [#7040](https://github.com/facebook/react/pull/7040))
|
||||
- Fix webcomponents example. ([@jalexanderfox](https://github.com/jalexanderfox) in [#7057](https://github.com/facebook/react/pull/7057))
|
||||
- Fix `unstable_renderSubtreeIntoContainer` so that context properly updates when linked to state. ([@gaearon](https://github.com/gaearon) in [#7125](https://github.com/facebook/react/pull/7125))
|
||||
- Improve invariant wording for void elements. ([@starkch](https://github.com/starkch) in [#7066](https://github.com/facebook/react/pull/7066))
|
||||
- Ensure no errors are thrown due to event handlers in server rendering. ([@rricard](https://github.com/rricard) in [#7127](https://github.com/facebook/react/pull/7127))
|
||||
- Fix regression resulting in `value`-less submit and reset inputs removing the browser-default text. ([@zpao](https://github.com/zpao) in [#7197](https://github.com/facebook/react/pull/7197))
|
||||
- Fix regression resulting in empty `name` attribute being added to inputs when not provided. ([@okonet](https://github.com/okonet) in [#7199](https://github.com/facebook/react/pull/7199))
|
||||
|
||||
### React Perf Add-on
|
||||
- Make `ReactPerf.start()` work properly during lifecycle methods. ([@gaearon](https://github.com/gaearon) in [#7208](https://github.com/facebook/react/pull/7208)).
|
||||
|
||||
### React CSSTransitionGroup Add-on
|
||||
- Fix issue resulting in spurious unknown property warnings. ([@batusai513](https://github.com/batusai513) in [#7165](https://github.com/facebook/react/pull/7165))
|
||||
|
||||
### React Native Renderer
|
||||
- Improve error handling in cross-platform touch event handling. ([@yungsters](https://github.com/yungsters) in [#7143](https://github.com/facebook/react/pull/7143))
|
||||
|
||||
|
||||
## 15.2.0 (July 1, 2016)
|
||||
|
||||
### React
|
||||
- Add error codes to production invariants, with links to the view the full error text. ([@keyanzhang](https://github.com/keyanzhang) in [#6948](https://github.com/facebook/react/pull/6948))
|
||||
- Include component stack information in PropType validation warnings. ([@spicyj](https://github.com/spicyj) in [#6771](https://github.com/facebook/react/pull/6771))
|
||||
- Include component stack information in PropType validation warnings. ([@troydemonbreun](https://github.com/troydemonbreun) in [#6398](https://github.com/facebook/react/pull/6398), [@spicyj](https://github.com/spicyj) in [#6771](https://github.com/facebook/react/pull/6771))
|
||||
- Include component stack information in key warnings. ([@keyanzhang](https://github.com/keyanzhang) in [#6799](https://github.com/facebook/react/pull/6799))
|
||||
- Stop validating props at mount time, only validate at element creation. ([@keyanzhang](https://github.com/keyanzhang) in [#6823](https://github.com/facebook/react/pull/6823))
|
||||
- Stop validating props at mount time, only validate at element creation. ([@keyanzhang](https://github.com/keyanzhang) in [#6824](https://github.com/facebook/react/pull/6824))
|
||||
- New invariant providing actionable error in missing instance case. ([@yungsters](https://github.com/yungsters) in [#6990](https://github.com/facebook/react/pull/6990))
|
||||
- Add `React.PropTypes.symbol` to support ES2015 Symbols as props. ([@puradox](https://github.com/puradox) in [#6377](https://github.com/facebook/react/pull/6377))
|
||||
- Fix incorrect coercion of ref or key that are undefined in development ([@gaearon](https://github.com/gaearon) in [#6880](https://github.com/facebook/react/pull/6880))
|
||||
- Fix a false positive when passing other element’s props to cloneElement ([@ericmatthys](https://github.com/ericmatthys) in [#6268](https://github.com/facebook/react/pull/6268))
|
||||
- Warn if you attempt to define `childContextTypes` on a functional component ([@Aweary](https://github.com/Aweary) in [#6933](https://github.com/facebook/react/pull/6933))
|
||||
|
||||
### React DOM
|
||||
- Add warning for unknown properties on DOM elements. ([@jimfb](https://github.com/jimfb) in [#6800](https://github.com/facebook/react/pull/6800), [@gm758](https://github.com/gm758) in [#7152](https://github.com/facebook/react/pull/7152))
|
||||
|
||||
+12
-3
@@ -52,11 +52,17 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks(npmTaskName);
|
||||
});
|
||||
|
||||
grunt.registerTask('eslint', require('./grunt/tasks/eslint'));
|
||||
grunt.registerTask('eslint', function() {
|
||||
// Use gulp here.
|
||||
spawnGulp(['eslint'], null, this.async());
|
||||
});
|
||||
|
||||
grunt.registerTask('lint', ['eslint']);
|
||||
|
||||
grunt.registerTask('flow', require('./grunt/tasks/flow'));
|
||||
grunt.registerTask('flow', function() {
|
||||
// Use gulp here.
|
||||
spawnGulp(['flow'], null, this.async());
|
||||
});
|
||||
|
||||
grunt.registerTask('delete-build-modules', function() {
|
||||
// Use gulp here.
|
||||
@@ -84,7 +90,10 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('npm-react-addons:release', npmReactAddonsTasks.buildReleases);
|
||||
grunt.registerTask('npm-react-addons:pack', npmReactAddonsTasks.packReleases);
|
||||
|
||||
grunt.registerTask('version-check', require('./grunt/tasks/version-check'));
|
||||
grunt.registerTask('version-check', function() {
|
||||
// Use gulp here.
|
||||
spawnGulp(['version-check'], null, this.async());
|
||||
});
|
||||
|
||||
grunt.registerTask('build:basic', [
|
||||
'build-modules',
|
||||
|
||||
@@ -65,6 +65,7 @@ The process to build `react.js` is built entirely on top of node.js, using many
|
||||
#### Prerequisites
|
||||
|
||||
* You have `node` installed at v4.0.0+ and `npm` at v2.0.0+.
|
||||
* You have `gcc` installed or are comfortable installing a compiler if needed. Some of our `npm` dependencies may require a compliation step. On OS X, the Xcode Command Line Tools will cover this. On Ubuntu, `apt-get install build-essential` will install the required packages. Similar commands should work on other Linux distros. Windows will require some additional steps, see the [`node-gyp` installation instructions](https://github.com/nodejs/node-gyp#installation) for details.
|
||||
* You are familiar with `npm` and know whether or not you need to use `sudo` when installing packages globally.
|
||||
* You are familiar with `git`.
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Fiber Example</title>
|
||||
<link rel="stylesheet" href="../shared/css/base.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Fiber Example</h1>
|
||||
<div id="container">
|
||||
<p>
|
||||
To install React, follow the instructions on
|
||||
<a href="https://github.com/facebook/react/">GitHub</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you can see this, React is <strong>not</strong> working right.
|
||||
If you checked out the source from GitHub make sure to run <code>grunt</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/react.js"></script>
|
||||
<script src="../../build/react-dom.js"></script>
|
||||
<script>
|
||||
function ExampleApplication(props) {
|
||||
var elapsed = Math.round(props.elapsed / 100);
|
||||
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
|
||||
var message =
|
||||
'React has been successfully running for ' + seconds + ' seconds.';
|
||||
|
||||
return React.DOM.p(null, message);
|
||||
}
|
||||
|
||||
// Call React.createFactory instead of directly call ExampleApplication({...}) in React.render
|
||||
var ExampleApplicationFactory = React.createFactory(ExampleApplication);
|
||||
|
||||
var start = new Date().getTime();
|
||||
setInterval(function() {
|
||||
ReactDOM.render(
|
||||
ExampleApplicationFactory({elapsed: new Date().getTime() - start}),
|
||||
document.getElementById('container')
|
||||
);
|
||||
}, 50);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
// Define WebComponent
|
||||
var proto = Object.create(HTMLElement.prototype, {
|
||||
createdCallback: {
|
||||
attachedCallback: {
|
||||
value: function() {
|
||||
var mountPoint = document.createElement('span');
|
||||
this.createShadowRoot().appendChild(mountPoint);
|
||||
|
||||
Vendored
-1
@@ -38,7 +38,6 @@ declare module 'UIManager' {
|
||||
declare function removeSubviewsFromContainerWithID() : void;
|
||||
declare function replaceExistingNonRootView() : void;
|
||||
declare function setChildren() : void;
|
||||
declare function setChildren() : void;
|
||||
declare function updateView() : void;
|
||||
}
|
||||
declare module 'View' { }
|
||||
@@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
var extension = process.platform === 'win32' ? '.cmd': '';
|
||||
|
||||
module.exports = function() {
|
||||
var done = this.async();
|
||||
grunt.util.spawn({
|
||||
cmd: 'node_modules/.bin/eslint' + extension,
|
||||
args: ['.'],
|
||||
opts: {stdio: 'inherit'}, // allows colors to passthrough
|
||||
}, function(err, result, code) {
|
||||
if (err) {
|
||||
grunt.log.error('Lint failed');
|
||||
} else {
|
||||
grunt.log.ok('Lint passed');
|
||||
}
|
||||
|
||||
done(code === 0);
|
||||
});
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
var extension = process.platform === 'win32' ? '.cmd': '';
|
||||
|
||||
module.exports = function() {
|
||||
var done = this.async();
|
||||
grunt.util.spawn({
|
||||
cmd: 'node_modules/.bin/flow' + extension,
|
||||
args: ['check', '.'],
|
||||
opts: {stdio: 'inherit'},
|
||||
}, function(err, result, code) {
|
||||
if (err) {
|
||||
grunt.log.error('Flow failed');
|
||||
} else {
|
||||
grunt.log.ok('Flow passed');
|
||||
}
|
||||
|
||||
done(code === 0);
|
||||
});
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
module.exports = function() {
|
||||
var pkgVersion = grunt.config.data.pkg.version;
|
||||
|
||||
var addonsData = grunt.file.readJSON('./packages/react-addons/package.json');
|
||||
var versions = {
|
||||
'packages/react/package.json':
|
||||
grunt.file.readJSON('./packages/react/package.json').version,
|
||||
'packages/react-dom/package.json':
|
||||
grunt.file.readJSON('./packages/react-dom/package.json').version,
|
||||
'packages/react-native-renderer/package.json':
|
||||
grunt.file.readJSON('./packages/react-native-renderer/package.json').version,
|
||||
'packages/react-addons/package.json (version)': addonsData.version,
|
||||
// Get the "version" without the range bit
|
||||
'packages/react-addons/package.json (react dependency)': addonsData.peerDependencies.react.slice(1),
|
||||
'src/ReactVersion.js': require('../../src/ReactVersion'),
|
||||
};
|
||||
|
||||
// Return true (ok) or false (failed)
|
||||
return Object.keys(versions).reduce(function(prev, name) {
|
||||
var version = versions[name];
|
||||
var ok = true;
|
||||
if (version !== pkgVersion) {
|
||||
grunt.log.error(
|
||||
'%s version does not match package.json. Expected %s, saw %s.',
|
||||
name,
|
||||
pkgVersion,
|
||||
version
|
||||
);
|
||||
ok = false;
|
||||
}
|
||||
return prev && ok;
|
||||
}, true);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var extension = process.platform === 'win32' ? '.cmd' : '';
|
||||
|
||||
module.exports = function(gulp, plugins) {
|
||||
var gutil = plugins.util;
|
||||
|
||||
return function(done) {
|
||||
spawn(
|
||||
process.execPath,
|
||||
[
|
||||
path.join('node_modules', '.bin', 'eslint' + extension),
|
||||
'.',
|
||||
],
|
||||
{
|
||||
// Allow colors to pass through
|
||||
stdio: 'inherit',
|
||||
}
|
||||
).on('close', function(code) {
|
||||
if (code !== 0) {
|
||||
gutil.log(
|
||||
gutil.colors.red(
|
||||
'Lint failed'
|
||||
)
|
||||
);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
gutil.log(
|
||||
gutil.colors.green(
|
||||
'Lint passed'
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var extension = process.platform === 'win32' ? '.cmd' : '';
|
||||
|
||||
module.exports = function(gulp, plugins) {
|
||||
var gutil = plugins.util;
|
||||
|
||||
return function(done) {
|
||||
spawn(
|
||||
process.execPath,
|
||||
[
|
||||
path.join('node_modules', '.bin', 'flow' + extension),
|
||||
'check',
|
||||
'.',
|
||||
],
|
||||
{
|
||||
// Allow colors to pass through
|
||||
stdio: 'inherit',
|
||||
}
|
||||
).on('close', function(code) {
|
||||
if (code !== 0) {
|
||||
gutil.log(
|
||||
gutil.colors.red(
|
||||
'Flow failed'
|
||||
)
|
||||
);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
gutil.log(
|
||||
gutil.colors.green(
|
||||
'Flow passed'
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(gulp, plugins) {
|
||||
var gutil = plugins.util;
|
||||
|
||||
return function(done) {
|
||||
var reactVersion = require('../../package.json').version;
|
||||
|
||||
var addonsData = require('../../packages/react-addons/package.json');
|
||||
var versions = {
|
||||
'packages/react/package.json':
|
||||
require('../../packages/react/package.json').version,
|
||||
'packages/react-dom/package.json':
|
||||
require('../../packages/react-dom/package.json').version,
|
||||
'packages/react-native-renderer/package.json':
|
||||
require('../../packages/react-native-renderer/package.json').version,
|
||||
'packages/react-addons/package.json (version)': addonsData.version,
|
||||
'packages/react-addons/package.json (react dependency)':
|
||||
// Get the "version" without the range bit
|
||||
addonsData.peerDependencies.react.slice(1),
|
||||
'src/ReactVersion.js': require('../../src/ReactVersion'),
|
||||
};
|
||||
|
||||
var allVersionsMatch = true;
|
||||
Object.keys(versions).forEach(function(name) {
|
||||
var version = versions[name];
|
||||
if (version !== reactVersion) {
|
||||
allVersionsMatch = false;
|
||||
gutil.log(
|
||||
gutil.colors.red(
|
||||
'%s version does not match package.json. Expected %s, saw %s.'
|
||||
),
|
||||
name,
|
||||
reactVersion,
|
||||
version
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (!allVersionsMatch) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
};
|
||||
+15
@@ -18,6 +18,13 @@ var babelPluginModules = require('fbjs-scripts/babel-6/rewrite-modules');
|
||||
var extractErrors = require('./scripts/error-codes/gulp-extract-errors');
|
||||
var devExpressionWithCodes = require('./scripts/error-codes/dev-expression-with-codes');
|
||||
|
||||
// Load all of the Gulp plugins.
|
||||
var plugins = require('gulp-load-plugins')();
|
||||
|
||||
function getTask(name) {
|
||||
return require(`./gulp/tasks/${name}`)(gulp, plugins);
|
||||
}
|
||||
|
||||
var paths = {
|
||||
react: {
|
||||
src: [
|
||||
@@ -60,6 +67,14 @@ var babelOpts = {
|
||||
],
|
||||
};
|
||||
|
||||
gulp.task('eslint', getTask('eslint'));
|
||||
|
||||
gulp.task('lint', ['eslint']);
|
||||
|
||||
gulp.task('flow', getTask('flow'));
|
||||
|
||||
gulp.task('version-check', getTask('version-check'));
|
||||
|
||||
gulp.task('react:clean', function() {
|
||||
return del([paths.react.lib]);
|
||||
});
|
||||
|
||||
Generated
+1112
-617
File diff suppressed because it is too large.
Load diff
+4
-2
@@ -43,7 +43,7 @@
|
||||
"eslint-plugin-react-internal": "file:eslint-rules",
|
||||
"fbjs": "^0.8.1",
|
||||
"fbjs-scripts": "^0.6.0",
|
||||
"flow-bin": "^0.26.0",
|
||||
"flow-bin": "^0.27.0",
|
||||
"glob": "^6.0.1",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-cli": "^0.1.13",
|
||||
@@ -53,6 +53,7 @@
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^6.0.0",
|
||||
"gulp-flatten": "^0.2.0",
|
||||
"gulp-load-plugins": "^1.2.4",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gzip-js": "~0.3.2",
|
||||
"jest": "^12.1.1",
|
||||
@@ -78,7 +79,8 @@
|
||||
"linc": "git diff --name-only --diff-filter=ACMRTUB `git merge-base HEAD master` | grep '\\.js$' | xargs eslint --",
|
||||
"lint": "grunt lint",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"flow": "flow"
|
||||
},
|
||||
"jest": {
|
||||
"modulePathIgnorePatterns": [
|
||||
|
||||
@@ -135,5 +135,7 @@
|
||||
"133": "Touch object is missing identifier",
|
||||
"134": "Touch data should have been recorded on start",
|
||||
"135": "Cannot find single active touch",
|
||||
"136": "Attempted to update component `%s` that has already been unmounted (or failed to mount)."
|
||||
"136": "Attempted to update component `%s` that has already been unmounted (or failed to mount).",
|
||||
"137": "%s is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.%s",
|
||||
"138": "Touch object is missing identifier."
|
||||
}
|
||||
@@ -12,13 +12,13 @@
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
|
||||
|
||||
describe('renderSubtreeIntoContainer', function() {
|
||||
|
||||
it('should pass context when rendering subtree elsewhere', function() {
|
||||
|
||||
var portal = document.createElement('div');
|
||||
|
||||
var Component = React.createClass({
|
||||
@@ -92,4 +92,107 @@ describe('renderSubtreeIntoContainer', function() {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update context if it changes due to setState', function() {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.state.bar,
|
||||
getFoo: () => this.state.bar,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
bar: 'initial',
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
},
|
||||
});
|
||||
|
||||
var instance = ReactDOM.render(<Parent />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('initial-initial');
|
||||
instance.setState({bar: 'changed'});
|
||||
expect(portal.firstChild.innerHTML).toBe('changed-changed');
|
||||
});
|
||||
|
||||
it('should update context if it changes due to re-render', function() {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.bar,
|
||||
getFoo: () => this.props.bar,
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(<Parent bar="initial" />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('initial-initial');
|
||||
ReactDOM.render(<Parent bar="changed" />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('changed-changed');
|
||||
});
|
||||
|
||||
});
|
||||
File renamed without changes.
@@ -266,6 +266,7 @@ var ReactTransitionGroup = React.createClass({
|
||||
delete props.childFactory;
|
||||
delete props.transitionLeaveTimeout;
|
||||
delete props.transitionEnterTimeout;
|
||||
delete props.transitionAppearTimeout;
|
||||
delete props.component;
|
||||
|
||||
return React.createElement(
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
|
||||
var invariant = require('invariant');
|
||||
@@ -72,10 +71,13 @@ function checkReactTypeSpec(typeSpecs, values, location, componentName, element,
|
||||
|
||||
var componentStackInfo = '';
|
||||
|
||||
if (debugID !== null) {
|
||||
componentStackInfo = ReactComponentTreeDevtool.getStackAddendumByID(debugID);
|
||||
} else if (element !== null) {
|
||||
componentStackInfo = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
|
||||
if (__DEV__) {
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
if (debugID !== null) {
|
||||
componentStackInfo = ReactComponentTreeDevtool.getStackAddendumByID(debugID);
|
||||
} else if (element !== null) {
|
||||
componentStackInfo = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
|
||||
}
|
||||
}
|
||||
|
||||
warning(
|
||||
|
||||
-5
@@ -32,10 +32,6 @@ function updateTree(id, update) {
|
||||
isMounted: false,
|
||||
updateCount: 0,
|
||||
};
|
||||
// TODO: We need to do this awkward dance because TopLevelWrapper "never
|
||||
// gets mounted" but its display name gets set in instantiateReactComponent
|
||||
// before its debug ID is set to 0.
|
||||
unmountedIDs[id] = true;
|
||||
}
|
||||
update(tree[id]);
|
||||
}
|
||||
@@ -148,7 +144,6 @@ var ReactComponentTreeDevtool = {
|
||||
|
||||
onMountComponent(id) {
|
||||
updateTree(id, item => item.isMounted = true);
|
||||
delete unmountedIDs[id];
|
||||
},
|
||||
|
||||
onMountRootComponent(id) {
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
var warning = require('warning');
|
||||
|
||||
function warnTDZ(publicInstance, callerName) {
|
||||
function warnNoop(publicInstance, callerName) {
|
||||
if (__DEV__) {
|
||||
var constructor = publicInstance.constructor;
|
||||
warning(
|
||||
false,
|
||||
'%s(...): Can only update a mounted or mounting component. ' +
|
||||
@@ -22,7 +23,7 @@ function warnTDZ(publicInstance, callerName) {
|
||||
'This is a no-op. Please check the code for the %s component.',
|
||||
callerName,
|
||||
callerName,
|
||||
publicInstance.constructor && publicInstance.constructor.displayName || ''
|
||||
constructor && (constructor.displayName || constructor.name) || 'ReactClass'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,7 @@ var ReactNoopUpdateQueue = {
|
||||
* @internal
|
||||
*/
|
||||
enqueueForceUpdate: function(publicInstance) {
|
||||
warnTDZ(publicInstance, 'forceUpdate');
|
||||
warnNoop(publicInstance, 'forceUpdate');
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -82,7 +83,7 @@ var ReactNoopUpdateQueue = {
|
||||
* @internal
|
||||
*/
|
||||
enqueueReplaceState: function(publicInstance, completeState) {
|
||||
warnTDZ(publicInstance, 'replaceState');
|
||||
warnNoop(publicInstance, 'replaceState');
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -96,7 +97,7 @@ var ReactNoopUpdateQueue = {
|
||||
* @internal
|
||||
*/
|
||||
enqueueSetState: function(publicInstance, partialState) {
|
||||
warnTDZ(publicInstance, 'setState');
|
||||
warnNoop(publicInstance, 'setState');
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -362,8 +362,6 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
test Inner(name: 'foo'), 'DIV', 'foo'
|
||||
expect(-> instance.replaceState {}).toThrow()
|
||||
expect(-> instance.isMounted()).toThrow()
|
||||
expect(-> instance.setProps name: 'bar').toThrow()
|
||||
expect(-> instance.replaceProps name: 'bar').toThrow()
|
||||
expect(console.error.calls.count()).toBe 2
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
|
||||
@@ -402,8 +402,6 @@ describe('ReactES6Class', function() {
|
||||
var instance = test(<Inner name="foo" />, 'DIV', 'foo');
|
||||
expect(() => instance.replaceState({})).toThrow();
|
||||
expect(() => instance.isMounted()).toThrow();
|
||||
expect(() => instance.setProps({name: 'bar'})).toThrow();
|
||||
expect(() => instance.replaceProps({name: 'bar'})).toThrow();
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
|
||||
@@ -489,8 +489,6 @@ describe('ReactTypeScriptClass', function() {
|
||||
);
|
||||
expect(() => instance.replaceState({})).toThrow();
|
||||
expect(() => instance.isMounted()).toThrow();
|
||||
expect(() => instance.setProps({ name: 'bar' })).toThrow();
|
||||
expect(() => instance.replaceProps({ name: 'bar' })).toThrow();
|
||||
expect((<any>console.error).calls.count()).toBe(2);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
|
||||
@@ -20,6 +20,7 @@ var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
|
||||
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
@@ -287,10 +288,11 @@ var ReactMount = {
|
||||
_updateRootComponent: function(
|
||||
prevComponent,
|
||||
nextElement,
|
||||
nextContext,
|
||||
container,
|
||||
callback) {
|
||||
ReactMount.scrollMonitor(container, function() {
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement, nextContext);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
|
||||
}
|
||||
@@ -313,10 +315,6 @@ var ReactMount = {
|
||||
shouldReuseMarkup,
|
||||
context
|
||||
) {
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
|
||||
// Various parts of our code (such as ReactCompositeComponent's
|
||||
// _renderValidatedComponent) assume that calls to render aren't nested;
|
||||
// verify that that's the case.
|
||||
@@ -340,13 +338,7 @@ var ReactMount = {
|
||||
);
|
||||
|
||||
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
|
||||
var componentInstance = instantiateReactComponent(nextElement);
|
||||
|
||||
if (__DEV__) {
|
||||
// Mute future events from the top level wrapper.
|
||||
// It is an implementation detail that devtools should not know about.
|
||||
componentInstance._debugID = 0;
|
||||
}
|
||||
var componentInstance = instantiateReactComponent(nextElement, false);
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
@@ -368,7 +360,6 @@ var ReactMount = {
|
||||
ReactInstrumentation.debugTool.onMountRootComponent(
|
||||
componentInstance._renderedComponent._debugID
|
||||
);
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
|
||||
return componentInstance;
|
||||
@@ -389,7 +380,7 @@ var ReactMount = {
|
||||
*/
|
||||
renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
|
||||
invariant(
|
||||
parentComponent != null && parentComponent._reactInternalInstance != null,
|
||||
parentComponent != null && ReactInstanceMap.has(parentComponent),
|
||||
'parentComponent must be a valid React Component'
|
||||
);
|
||||
return ReactMount._renderSubtreeIntoContainer(
|
||||
@@ -440,6 +431,14 @@ var ReactMount = {
|
||||
nextElement
|
||||
);
|
||||
|
||||
var nextContext;
|
||||
if (parentComponent) {
|
||||
var parentInst = ReactInstanceMap.get(parentComponent);
|
||||
nextContext = parentInst._processChildContext(parentInst._context);
|
||||
} else {
|
||||
nextContext = emptyObject;
|
||||
}
|
||||
|
||||
var prevComponent = getTopLevelWrapperInContainer(container);
|
||||
|
||||
if (prevComponent) {
|
||||
@@ -453,6 +452,7 @@ var ReactMount = {
|
||||
ReactMount._updateRootComponent(
|
||||
prevComponent,
|
||||
nextWrappedElement,
|
||||
nextContext,
|
||||
container,
|
||||
updatedCallback
|
||||
);
|
||||
@@ -501,11 +501,7 @@ var ReactMount = {
|
||||
nextWrappedElement,
|
||||
container,
|
||||
shouldReuseMarkup,
|
||||
parentComponent != null ?
|
||||
parentComponent._reactInternalInstance._processChildContext(
|
||||
parentComponent._reactInternalInstance._context
|
||||
) :
|
||||
emptyObject
|
||||
nextContext
|
||||
)._renderedComponent.getPublicInstance();
|
||||
if (callback) {
|
||||
callback.call(component);
|
||||
|
||||
@@ -15,7 +15,9 @@ var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactInputSelection = require('ReactInputSelection');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var Transaction = require('Transaction');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
|
||||
/**
|
||||
@@ -90,6 +92,13 @@ var TRANSACTION_WRAPPERS = [
|
||||
ON_DOM_READY_QUEUEING,
|
||||
];
|
||||
|
||||
if (__DEV__) {
|
||||
TRANSACTION_WRAPPERS.push({
|
||||
initialize: ReactInstrumentation.debugTool.onBeginFlush,
|
||||
close: ReactInstrumentation.debugTool.onEndFlush,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently:
|
||||
* - The order that these are listed in the transaction is critical:
|
||||
@@ -104,7 +113,7 @@ var TRANSACTION_WRAPPERS = [
|
||||
*
|
||||
* @class ReactReconcileTransaction
|
||||
*/
|
||||
function ReactReconcileTransaction(useCreateElement) {
|
||||
function ReactReconcileTransaction(useCreateElement: boolean) {
|
||||
this.reinitializeTransaction();
|
||||
// Only server-side rendering really needs this option (see
|
||||
// `ReactServerRendering`), but server-side uses
|
||||
@@ -135,6 +144,13 @@ var Mixin = {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return ReactUpdateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save current transaction state -- if the return value from this method is
|
||||
* passed to `rollback`, the transaction will be reset to that state.
|
||||
|
||||
@@ -22,7 +22,6 @@ var warning = require('warning');
|
||||
|
||||
var didWarnValueLink = false;
|
||||
var didWarnCheckedLink = false;
|
||||
var didWarnValueNull = false;
|
||||
var didWarnValueDefaultValue = false;
|
||||
var didWarnCheckedDefaultChecked = false;
|
||||
var didWarnControlledToUncontrolled = false;
|
||||
@@ -35,19 +34,6 @@ function forceUpdateIfMounted() {
|
||||
}
|
||||
}
|
||||
|
||||
function warnIfValueIsNull(props) {
|
||||
if (props != null && props.value === null && !didWarnValueNull) {
|
||||
warning(
|
||||
false,
|
||||
'`value` prop on `input` should not be null. ' +
|
||||
'Consider using the empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.'
|
||||
);
|
||||
|
||||
didWarnValueNull = true;
|
||||
}
|
||||
}
|
||||
|
||||
function isControlled(props) {
|
||||
var usesChecked = props.type === 'checkbox' || props.type === 'radio';
|
||||
return usesChecked ? props.checked !== undefined : props.value !== undefined;
|
||||
@@ -149,7 +135,6 @@ var ReactDOMInput = {
|
||||
);
|
||||
didWarnValueDefaultValue = true;
|
||||
}
|
||||
warnIfValueIsNull(props);
|
||||
}
|
||||
|
||||
var defaultValue = props.defaultValue;
|
||||
@@ -169,8 +154,6 @@ var ReactDOMInput = {
|
||||
var props = inst._currentElement.props;
|
||||
|
||||
if (__DEV__) {
|
||||
warnIfValueIsNull(props);
|
||||
|
||||
var controlled = isControlled(props);
|
||||
var owner = inst._currentElement._owner;
|
||||
|
||||
@@ -233,10 +216,20 @@ var ReactDOMInput = {
|
||||
},
|
||||
|
||||
postMountWrapper: function(inst) {
|
||||
var props = inst._currentElement.props;
|
||||
|
||||
// This is in postMount because we need access to the DOM node, which is not
|
||||
// available until after the component has mounted.
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
node.value = node.value; // Detach value from defaultValue
|
||||
|
||||
// Detach value from defaultValue. We won't do anything if we're working on
|
||||
// submit or reset inputs as those values & defaultValues are linked. They
|
||||
// are not resetable nodes so this operation doesn't matter and actually
|
||||
// removes browser-default values (eg "Submit Query") when no value is
|
||||
// provided.
|
||||
if (props.type !== 'submit' && props.type !== 'reset') {
|
||||
node.value = node.value;
|
||||
}
|
||||
|
||||
// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
|
||||
// this is needed to work around a chrome bug where setting defaultChecked
|
||||
@@ -244,10 +237,14 @@ var ReactDOMInput = {
|
||||
// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
|
||||
// We need to temporarily unset name to avoid disrupting radio button groups.
|
||||
var name = node.name;
|
||||
node.name = undefined;
|
||||
if (name !== '') {
|
||||
node.name = '';
|
||||
}
|
||||
node.defaultChecked = !node.defaultChecked;
|
||||
node.defaultChecked = !node.defaultChecked;
|
||||
node.name = name;
|
||||
if (name !== '') {
|
||||
node.name = name;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ var ReactUpdates = require('ReactUpdates');
|
||||
var warning = require('warning');
|
||||
|
||||
var didWarnValueLink = false;
|
||||
var didWarnValueNull = false;
|
||||
var didWarnValueDefaultValue = false;
|
||||
|
||||
function updateOptionsIfPendingUpdateAndMounted() {
|
||||
@@ -45,19 +44,6 @@ function getDeclarationErrorAddendum(owner) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function warnIfValueIsNull(props) {
|
||||
if (props != null && props.value === null && !didWarnValueNull) {
|
||||
warning(
|
||||
false,
|
||||
'`value` prop on `select` should not be null. ' +
|
||||
'Consider using the empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.'
|
||||
);
|
||||
|
||||
didWarnValueNull = true;
|
||||
}
|
||||
}
|
||||
|
||||
var valuePropNames = ['value', 'defaultValue'];
|
||||
|
||||
/**
|
||||
@@ -168,7 +154,6 @@ var ReactDOMSelect = {
|
||||
mountWrapper: function(inst, props) {
|
||||
if (__DEV__) {
|
||||
checkSelectPropTypes(inst, props);
|
||||
warnIfValueIsNull(props);
|
||||
}
|
||||
|
||||
var value = LinkedValueUtils.getValue(props);
|
||||
@@ -205,9 +190,6 @@ var ReactDOMSelect = {
|
||||
|
||||
postUpdateWrapper: function(inst) {
|
||||
var props = inst._currentElement.props;
|
||||
if (__DEV__) {
|
||||
warnIfValueIsNull(props);
|
||||
}
|
||||
|
||||
// After the initial mount, we control selected-ness manually so don't pass
|
||||
// this value down
|
||||
|
||||
@@ -20,7 +20,6 @@ var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
|
||||
var didWarnValueLink = false;
|
||||
var didWarnValueNull = false;
|
||||
var didWarnValDefaultVal = false;
|
||||
|
||||
function forceUpdateIfMounted() {
|
||||
@@ -30,19 +29,6 @@ function forceUpdateIfMounted() {
|
||||
}
|
||||
}
|
||||
|
||||
function warnIfValueIsNull(props) {
|
||||
if (props != null && props.value === null && !didWarnValueNull) {
|
||||
warning(
|
||||
false,
|
||||
'`value` prop on `textarea` should not be null. ' +
|
||||
'Consider using the empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.'
|
||||
);
|
||||
|
||||
didWarnValueNull = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a <textarea> host component that allows setting `value`, and
|
||||
* `defaultValue`. This differs from the traditional DOM API because value is
|
||||
@@ -109,7 +95,6 @@ var ReactDOMTextarea = {
|
||||
);
|
||||
didWarnValDefaultVal = true;
|
||||
}
|
||||
warnIfValueIsNull(props);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,10 +144,6 @@ var ReactDOMTextarea = {
|
||||
updateWrapper: function(inst) {
|
||||
var props = inst._currentElement.props;
|
||||
|
||||
if (__DEV__) {
|
||||
warnIfValueIsNull(props);
|
||||
}
|
||||
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
var value = LinkedValueUtils.getValue(props);
|
||||
if (value != null) {
|
||||
|
||||
@@ -102,6 +102,35 @@ describe('ReactDOMInput', function() {
|
||||
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
|
||||
});
|
||||
|
||||
it('should render name attribute if it is supplied', function() {
|
||||
var container = document.createElement('div');
|
||||
var node = ReactDOM.render(<input type="text" name="name" />, container);
|
||||
expect(node.name).toBe('name');
|
||||
expect(container.firstChild.getAttribute('name')).toBe('name');
|
||||
});
|
||||
|
||||
it('should render name attribute if it is supplied for SSR', function() {
|
||||
var element = <input type="text" name="name" />;
|
||||
var markup = ReactDOMServer.renderToString(element);
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
expect(div.firstChild.getAttribute('name')).toBe('name');
|
||||
});
|
||||
|
||||
it('should not render name attribute if it is not supplied', function() {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<input type="text" />, container);
|
||||
expect(container.firstChild.getAttribute('name')).toBe(null);
|
||||
});
|
||||
|
||||
it('should not render name attribute if it is not supplied for SSR', function() {
|
||||
var element = <input type="text" />;
|
||||
var markup = ReactDOMServer.renderToString(element);
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
expect(div.firstChild.getAttribute('name')).toBe(null);
|
||||
});
|
||||
|
||||
it('should display "foobar" for `defaultValue` of `objToString`', function() {
|
||||
var objToString = {
|
||||
toString: function() {
|
||||
@@ -672,7 +701,7 @@ describe('ReactDOMInput', function() {
|
||||
value="value"
|
||||
defaultChecked={true}
|
||||
checked={false}
|
||||
onChange={() => null}
|
||||
onChange={() => null}
|
||||
/>, container);
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
@@ -718,10 +747,8 @@ describe('ReactDOMInput', function() {
|
||||
'set type',
|
||||
'set value',
|
||||
'set value',
|
||||
'set name',
|
||||
'set checked',
|
||||
'set checked',
|
||||
'set name',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDOMFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { HostChildren } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
|
||||
type DOMContainerElement = Element & { _reactRootContainer: Object };
|
||||
|
||||
type Container = Element;
|
||||
type Props = { };
|
||||
type Instance = Element;
|
||||
|
||||
function recursivelyAppendChildren(parent : Element, child : HostChildren<Instance>) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
/* $FlowFixMe: Element should have this property. */
|
||||
if (child.nodeType === 1) {
|
||||
/* $FlowFixMe: Refinement issue. I don't know how to express different. */
|
||||
parent.appendChild(child);
|
||||
} else {
|
||||
/* As a result of the refinement issue this type isn't known. */
|
||||
let node : any = child;
|
||||
do {
|
||||
recursivelyAppendChildren(parent, node.output);
|
||||
} while (node = node.sibling);
|
||||
}
|
||||
}
|
||||
|
||||
var DOMRenderer = ReactFiberReconciler({
|
||||
|
||||
updateContainer(container : Container, children : HostChildren<Instance>) : void {
|
||||
container.innerHTML = '';
|
||||
recursivelyAppendChildren(container, children);
|
||||
},
|
||||
|
||||
createInstance(type : string, props : Props, children : HostChildren<Instance>) : Instance {
|
||||
const domElement = document.createElement(type);
|
||||
recursivelyAppendChildren(domElement, children);
|
||||
if (typeof props.children === 'string') {
|
||||
domElement.textContent = props.children;
|
||||
}
|
||||
return domElement;
|
||||
},
|
||||
|
||||
prepareUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : boolean {
|
||||
return true;
|
||||
},
|
||||
|
||||
commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : void {
|
||||
domElement.innerHTML = '';
|
||||
recursivelyAppendChildren(domElement, children);
|
||||
if (typeof newProps.children === 'string') {
|
||||
domElement.textContent = newProps.children;
|
||||
}
|
||||
},
|
||||
|
||||
deleteInstance(instance : Instance) : void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
scheduleHighPriCallback: window.requestAnimationFrame,
|
||||
|
||||
scheduleLowPriCallback: window.requestIdleCallback,
|
||||
|
||||
});
|
||||
|
||||
var ReactDOM = {
|
||||
|
||||
render(element : ReactElement<any>, container : DOMContainerElement) {
|
||||
if (!container._reactRootContainer) {
|
||||
container._reactRootContainer = DOMRenderer.mountContainer(element, container);
|
||||
} else {
|
||||
DOMRenderer.updateContainer(element, container._reactRootContainer);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactDOM;
|
||||
@@ -37,10 +37,7 @@ function renderToStringImpl(element, makeStaticMarkup) {
|
||||
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
|
||||
|
||||
return transaction.perform(function() {
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
var componentInstance = instantiateReactComponent(element);
|
||||
var componentInstance = instantiateReactComponent(element, true);
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance,
|
||||
transaction,
|
||||
@@ -52,7 +49,6 @@ function renderToStringImpl(element, makeStaticMarkup) {
|
||||
ReactInstrumentation.debugTool.onUnmountComponent(
|
||||
componentInstance._debugID
|
||||
);
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
if (!makeStaticMarkup) {
|
||||
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
var PooledClass = require('PooledClass');
|
||||
var Transaction = require('Transaction');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactServerUpdateQueue = require('ReactServerUpdateQueue');
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,6 +24,13 @@ var Transaction = require('Transaction');
|
||||
*/
|
||||
var TRANSACTION_WRAPPERS = [];
|
||||
|
||||
if (__DEV__) {
|
||||
TRANSACTION_WRAPPERS.push({
|
||||
initialize: ReactInstrumentation.debugTool.onBeginFlush,
|
||||
close: ReactInstrumentation.debugTool.onEndFlush,
|
||||
});
|
||||
}
|
||||
|
||||
var noopCallbackQueue = {
|
||||
enqueue: function() {},
|
||||
};
|
||||
@@ -34,6 +43,7 @@ function ReactServerRenderingTransaction(renderToStaticMarkup) {
|
||||
this.reinitializeTransaction();
|
||||
this.renderToStaticMarkup = renderToStaticMarkup;
|
||||
this.useCreateElement = false;
|
||||
this.updateQueue = new ReactServerUpdateQueue(this);
|
||||
}
|
||||
|
||||
var Mixin = {
|
||||
@@ -54,6 +64,13 @@ var Mixin = {
|
||||
return noopCallbackQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return this.updateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for this, and will invoke this before allowing this
|
||||
* instance to be reused.
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactServerUpdateQueue
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
var Transaction = require('Transaction');
|
||||
var warning = require('warning');
|
||||
|
||||
function warnNoop(publicInstance: ReactComponent<any, any, any>, callerName: string) {
|
||||
if (__DEV__) {
|
||||
var constructor = publicInstance.constructor;
|
||||
warning(
|
||||
false,
|
||||
'%s(...): Can only update a mounting component. ' +
|
||||
'This usually means you called %s() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op. Please check the code for the %s component.',
|
||||
callerName,
|
||||
callerName,
|
||||
constructor && (constructor.displayName || constructor.name) || 'ReactClass'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the update queue used for server rendering.
|
||||
* It delegates to ReactUpdateQueue while server rendering is in progress and
|
||||
* switches to ReactNoopUpdateQueue after the transaction has completed.
|
||||
* @class ReactServerUpdateQueue
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
class ReactServerUpdateQueue {
|
||||
/* :: transaction: Transaction; */
|
||||
|
||||
constructor(transaction: Transaction) {
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not this composite component is mounted.
|
||||
* @param {ReactClass} publicInstance The instance we want to test.
|
||||
* @return {boolean} True if mounted, false otherwise.
|
||||
* @protected
|
||||
* @final
|
||||
*/
|
||||
isMounted(publicInstance: ReactComponent<any, any, any>): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a callback that will be executed after all the pending updates
|
||||
* have processed.
|
||||
*
|
||||
* @param {ReactClass} publicInstance The instance to use as `this` context.
|
||||
* @param {?function} callback Called after state is updated.
|
||||
* @internal
|
||||
*/
|
||||
enqueueCallback(publicInstance: ReactComponent<any, any, any>, callback?: Function, callerName?: string) {
|
||||
if (this.transaction.isInTransaction()) {
|
||||
ReactUpdateQueue.enqueueCallback(publicInstance, callback, callerName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces an update. This should only be invoked when it is known with
|
||||
* certainty that we are **not** in a DOM transaction.
|
||||
*
|
||||
* You may want to call this when you know that some deeper aspect of the
|
||||
* component's state has changed but `setState` was not called.
|
||||
*
|
||||
* This will not invoke `shouldComponentUpdate`, but it will invoke
|
||||
* `componentWillUpdate` and `componentDidUpdate`.
|
||||
*
|
||||
* @param {ReactClass} publicInstance The instance that should rerender.
|
||||
* @internal
|
||||
*/
|
||||
enqueueForceUpdate(publicInstance: ReactComponent<any, any, any>) {
|
||||
if (this.transaction.isInTransaction()) {
|
||||
ReactUpdateQueue.enqueueForceUpdate(publicInstance);
|
||||
} else {
|
||||
warnNoop(publicInstance, 'forceUpdate');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all of the state. Always use this or `setState` to mutate state.
|
||||
* You should treat `this.state` as immutable.
|
||||
*
|
||||
* There is no guarantee that `this.state` will be immediately updated, so
|
||||
* accessing `this.state` after calling this method may return the old value.
|
||||
*
|
||||
* @param {ReactClass} publicInstance The instance that should rerender.
|
||||
* @param {object|function} completeState Next state.
|
||||
* @internal
|
||||
*/
|
||||
enqueueReplaceState(publicInstance: ReactComponent<any, any, any>, completeState: Object|Function) {
|
||||
if (this.transaction.isInTransaction()) {
|
||||
ReactUpdateQueue.enqueueReplaceState(publicInstance, completeState);
|
||||
} else {
|
||||
warnNoop(publicInstance, 'replaceState');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a subset of the state. This only exists because _pendingState is
|
||||
* internal. This provides a merging strategy that is not available to deep
|
||||
* properties which is confusing. TODO: Expose pendingState or don't use it
|
||||
* during the merge.
|
||||
*
|
||||
* @param {ReactClass} publicInstance The instance that should rerender.
|
||||
* @param {object|function} partialState Next partial state to be merged with state.
|
||||
* @internal
|
||||
*/
|
||||
enqueueSetState(publicInstance: ReactComponent<any, any, any>, partialState: Object|Function) {
|
||||
if (this.transaction.isInTransaction()) {
|
||||
ReactUpdateQueue.enqueueSetState(publicInstance, partialState);
|
||||
} else {
|
||||
warnNoop(publicInstance, 'setState');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactServerUpdateQueue;
|
||||
@@ -400,4 +400,82 @@ describe('ReactServerRendering', function() {
|
||||
expect(markup.indexOf('hello, world') >= 0).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('warns with a no-op when an async setState is triggered', function() {
|
||||
class Foo extends React.Component {
|
||||
componentWillMount() {
|
||||
this.setState({text: 'hello'});
|
||||
setTimeout(() => {
|
||||
this.setState({text: 'error'});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return <div onClick={() => {}}>{this.state.text}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
ReactServerRendering.renderToString(<Foo />);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: setState(...): Can only update a mounting component.' +
|
||||
' This usually means you called setState() outside componentWillMount() on the server.' +
|
||||
' This is a no-op. Please check the code for the Foo component.'
|
||||
);
|
||||
var markup = ReactServerRendering.renderToStaticMarkup(<Foo />);
|
||||
expect(markup).toBe('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('warns with a no-op when an async replaceState is triggered', function() {
|
||||
var Bar = React.createClass({
|
||||
componentWillMount: function() {
|
||||
this.replaceState({text: 'hello'});
|
||||
setTimeout(() => {
|
||||
this.replaceState({text: 'error'});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return <div onClick={() => {}}>{this.state.text}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
spyOn(console, 'error');
|
||||
ReactServerRendering.renderToString(<Bar />);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: replaceState(...): Can only update a mounting component. ' +
|
||||
'This usually means you called replaceState() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op. Please check the code for the Bar component.'
|
||||
);
|
||||
var markup = ReactServerRendering.renderToStaticMarkup(<Bar />);
|
||||
expect(markup).toBe('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('warns with a no-op when an async forceUpdate is triggered', function() {
|
||||
var Baz = React.createClass({
|
||||
componentWillMount: function() {
|
||||
this.forceUpdate();
|
||||
setTimeout(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return <div onClick={() => {}}></div>;
|
||||
},
|
||||
});
|
||||
|
||||
spyOn(console, 'error');
|
||||
ReactServerRendering.renderToString(<Baz />);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: forceUpdate(...): Can only update a mounting component. ' +
|
||||
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op. Please check the code for the Baz component.'
|
||||
);
|
||||
var markup = ReactServerRendering.renderToStaticMarkup(<Baz />);
|
||||
expect(markup).toBe('<div></div>');
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,6 @@ var DOMPropertyInjection = {
|
||||
* specifies how the associated DOM property should be accessed or rendered.
|
||||
*/
|
||||
MUST_USE_PROPERTY: 0x1,
|
||||
HAS_SIDE_EFFECTS: 0x2,
|
||||
HAS_BOOLEAN_VALUE: 0x4,
|
||||
HAS_NUMERIC_VALUE: 0x8,
|
||||
HAS_POSITIVE_NUMERIC_VALUE: 0x10 | 0x8,
|
||||
@@ -91,7 +90,6 @@ var DOMPropertyInjection = {
|
||||
mutationMethod: null,
|
||||
|
||||
mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
|
||||
hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS),
|
||||
hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
|
||||
hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE),
|
||||
hasPositiveNumericValue:
|
||||
@@ -99,12 +97,6 @@ var DOMPropertyInjection = {
|
||||
hasOverloadedBooleanValue:
|
||||
checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
|
||||
};
|
||||
|
||||
invariant(
|
||||
propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects,
|
||||
'DOMProperty: Properties that have side effects must use property: %s',
|
||||
propName
|
||||
);
|
||||
invariant(
|
||||
propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue +
|
||||
propertyInfo.hasOverloadedBooleanValue <= 1,
|
||||
@@ -183,11 +175,6 @@ var DOMProperty = {
|
||||
* initial render.
|
||||
* mustUseProperty:
|
||||
* Whether the property must be accessed and mutated as an object property.
|
||||
* hasSideEffects:
|
||||
* Whether or not setting a value causes side effects such as triggering
|
||||
* resources to be loaded or text selection changes. If true, we read from
|
||||
* the DOM before updating to ensure that the value is only set if it has
|
||||
* changed.
|
||||
* hasBooleanValue:
|
||||
* Whether the property should be removed when set to a falsey value.
|
||||
* hasNumericValue:
|
||||
|
||||
@@ -145,15 +145,9 @@ var DOMPropertyOperations = {
|
||||
this.deleteValueForProperty(node, name);
|
||||
return;
|
||||
} else if (propertyInfo.mustUseProperty) {
|
||||
var propName = propertyInfo.propertyName;
|
||||
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
|
||||
// property type before comparing; only `value` does and is string.
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== ('' + value)) {
|
||||
// Contrary to `setAttribute`, object properties are properly
|
||||
// `toString`ed by IE8/9.
|
||||
node[propName] = value;
|
||||
}
|
||||
// Contrary to `setAttribute`, object properties are properly
|
||||
// `toString`ed by IE8/9.
|
||||
node[propertyInfo.propertyName] = value;
|
||||
} else {
|
||||
var attributeName = propertyInfo.attributeName;
|
||||
var namespace = propertyInfo.attributeNamespace;
|
||||
@@ -240,13 +234,9 @@ var DOMPropertyOperations = {
|
||||
} else if (propertyInfo.mustUseProperty) {
|
||||
var propName = propertyInfo.propertyName;
|
||||
if (propertyInfo.hasBooleanValue) {
|
||||
// No HAS_SIDE_EFFECTS logic here, only `value` has it and is string.
|
||||
node[propName] = false;
|
||||
} else {
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== '') {
|
||||
node[propName] = '';
|
||||
}
|
||||
node[propName] = '';
|
||||
}
|
||||
} else {
|
||||
node.removeAttribute(propertyInfo.attributeName);
|
||||
|
||||
@@ -16,137 +16,10 @@ var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
var createNodesFromMarkup = require('createNodesFromMarkup');
|
||||
var emptyFunction = require('emptyFunction');
|
||||
var getMarkupWrap = require('getMarkupWrap');
|
||||
var invariant = require('invariant');
|
||||
|
||||
var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/;
|
||||
var RESULT_INDEX_ATTR = 'data-danger-index';
|
||||
|
||||
/**
|
||||
* Extracts the `nodeName` from a string of markup.
|
||||
*
|
||||
* NOTE: Extracting the `nodeName` does not require a regular expression match
|
||||
* because we make assumptions about React-generated markup (i.e. there are no
|
||||
* spaces surrounding the opening tag and there is at least one attribute).
|
||||
*
|
||||
* @param {string} markup String of markup.
|
||||
* @return {string} Node name of the supplied markup.
|
||||
* @see http://jsperf.com/extract-nodename
|
||||
*/
|
||||
function getNodeName(markup) {
|
||||
return markup.substring(1, markup.indexOf(' '));
|
||||
}
|
||||
|
||||
var Danger = {
|
||||
|
||||
/**
|
||||
* Renders markup into an array of nodes. The markup is expected to render
|
||||
* into a list of root nodes. Also, the length of `resultList` and
|
||||
* `markupList` should be the same.
|
||||
*
|
||||
* @param {array<string>} markupList List of markup strings to render.
|
||||
* @return {array<DOMElement>} List of rendered nodes.
|
||||
* @internal
|
||||
*/
|
||||
dangerouslyRenderMarkup: function(markupList) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' +
|
||||
'thread. Make sure `window` and `document` are available globally ' +
|
||||
'before requiring React when unit testing or use ' +
|
||||
'ReactDOMServer.renderToString for server rendering.'
|
||||
);
|
||||
var nodeName;
|
||||
var markupByNodeName = {};
|
||||
// Group markup by `nodeName` if a wrap is necessary, else by '*'.
|
||||
for (var i = 0; i < markupList.length; i++) {
|
||||
invariant(
|
||||
markupList[i],
|
||||
'dangerouslyRenderMarkup(...): Missing markup.'
|
||||
);
|
||||
nodeName = getNodeName(markupList[i]);
|
||||
nodeName = getMarkupWrap(nodeName) ? nodeName : '*';
|
||||
markupByNodeName[nodeName] = markupByNodeName[nodeName] || [];
|
||||
markupByNodeName[nodeName][i] = markupList[i];
|
||||
}
|
||||
var resultList = [];
|
||||
var resultListAssignmentCount = 0;
|
||||
for (nodeName in markupByNodeName) {
|
||||
if (!markupByNodeName.hasOwnProperty(nodeName)) {
|
||||
continue;
|
||||
}
|
||||
var markupListByNodeName = markupByNodeName[nodeName];
|
||||
|
||||
// This for-in loop skips the holes of the sparse array. The order of
|
||||
// iteration should follow the order of assignment, which happens to match
|
||||
// numerical index order, but we don't rely on that.
|
||||
var resultIndex;
|
||||
for (resultIndex in markupListByNodeName) {
|
||||
if (markupListByNodeName.hasOwnProperty(resultIndex)) {
|
||||
var markup = markupListByNodeName[resultIndex];
|
||||
|
||||
// Push the requested markup with an additional RESULT_INDEX_ATTR
|
||||
// attribute. If the markup does not start with a < character, it
|
||||
// will be discarded below (with an appropriate console.error).
|
||||
markupListByNodeName[resultIndex] = markup.replace(
|
||||
OPEN_TAG_NAME_EXP,
|
||||
// This index will be parsed back out below.
|
||||
'$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" '
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Render each group of markup with similar wrapping `nodeName`.
|
||||
var renderNodes = createNodesFromMarkup(
|
||||
markupListByNodeName.join(''),
|
||||
emptyFunction // Do nothing special with <script> tags.
|
||||
);
|
||||
|
||||
for (var j = 0; j < renderNodes.length; ++j) {
|
||||
var renderNode = renderNodes[j];
|
||||
if (renderNode.hasAttribute &&
|
||||
renderNode.hasAttribute(RESULT_INDEX_ATTR)) {
|
||||
|
||||
resultIndex = +renderNode.getAttribute(RESULT_INDEX_ATTR);
|
||||
renderNode.removeAttribute(RESULT_INDEX_ATTR);
|
||||
|
||||
invariant(
|
||||
!resultList.hasOwnProperty(resultIndex),
|
||||
'Danger: Assigning to an already-occupied result index.'
|
||||
);
|
||||
|
||||
resultList[resultIndex] = renderNode;
|
||||
|
||||
// This should match resultList.length and markupList.length when
|
||||
// we're done.
|
||||
resultListAssignmentCount += 1;
|
||||
|
||||
} else if (__DEV__) {
|
||||
console.error(
|
||||
'Danger: Discarding unexpected node:',
|
||||
renderNode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Although resultList was populated out of order, it should now be a dense
|
||||
// array.
|
||||
invariant(
|
||||
resultListAssignmentCount === resultList.length,
|
||||
'Danger: Did not assign to every index of resultList.'
|
||||
);
|
||||
|
||||
invariant(
|
||||
resultList.length === markupList.length,
|
||||
'Danger: Expected markup to render %s nodes, but rendered %s.',
|
||||
markupList.length,
|
||||
resultList.length
|
||||
);
|
||||
|
||||
return resultList;
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces a node with a string of markup at its current position within its
|
||||
* parent. The markup must render into a single root node.
|
||||
|
||||
@@ -154,8 +154,8 @@ function assertValidProps(component, props) {
|
||||
if (voidElementTags[component._tag]) {
|
||||
invariant(
|
||||
props.children == null && props.dangerouslySetInnerHTML == null,
|
||||
'%s is a void element tag and must not have `children` or ' +
|
||||
'use `props.dangerouslySetInnerHTML`.%s',
|
||||
'%s is a void element tag and must neither have `children` nor ' +
|
||||
'use `dangerouslySetInnerHTML`.%s',
|
||||
component._tag,
|
||||
component._currentElement._owner ?
|
||||
' Check the render method of ' +
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDOMNullInputValuePropDevtool = require('ReactDOMNullInputValuePropDevtool');
|
||||
var ReactDOMUnknownPropertyDevtool = require('ReactDOMUnknownPropertyDevtool');
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
|
||||
@@ -20,23 +21,21 @@ var eventHandlers = [];
|
||||
var handlerDoesThrowForEvent = {};
|
||||
|
||||
function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) {
|
||||
if (__DEV__) {
|
||||
eventHandlers.forEach(function(handler) {
|
||||
try {
|
||||
if (handler[handlerFunctionName]) {
|
||||
handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
} catch (e) {
|
||||
warning(
|
||||
handlerDoesThrowForEvent[handlerFunctionName],
|
||||
'exception thrown by devtool while handling %s: %s',
|
||||
handlerFunctionName,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
handlerDoesThrowForEvent[handlerFunctionName] = true;
|
||||
eventHandlers.forEach(function(handler) {
|
||||
try {
|
||||
if (handler[handlerFunctionName]) {
|
||||
handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
warning(
|
||||
handlerDoesThrowForEvent[handlerFunctionName],
|
||||
'exception thrown by devtool while handling %s: %s',
|
||||
handlerFunctionName,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
handlerDoesThrowForEvent[handlerFunctionName] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var ReactDOMDebugTool = {
|
||||
@@ -68,5 +67,6 @@ var ReactDOMDebugTool = {
|
||||
};
|
||||
|
||||
ReactDOMDebugTool.addDevtool(ReactDOMUnknownPropertyDevtool);
|
||||
ReactDOMDebugTool.addDevtool(ReactDOMNullInputValuePropDevtool);
|
||||
|
||||
module.exports = ReactDOMDebugTool;
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDOMDebugTool = require('ReactDOMDebugTool');
|
||||
var debugTool = null;
|
||||
|
||||
module.exports = {debugTool: ReactDOMDebugTool};
|
||||
if (__DEV__) {
|
||||
var ReactDOMDebugTool = require('ReactDOMDebugTool');
|
||||
debugTool = ReactDOMDebugTool;
|
||||
}
|
||||
|
||||
module.exports = {debugTool};
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('Danger', function() {
|
||||
|
||||
describe('dangerouslyRenderMarkup', function() {
|
||||
var Danger;
|
||||
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
Danger = require('Danger');
|
||||
});
|
||||
|
||||
it('should render markup', function() {
|
||||
var markup = '<div data-reactid=".rX"></div>';
|
||||
var output = Danger.dangerouslyRenderMarkup([markup])[0];
|
||||
|
||||
expect(output.nodeName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('should render markup with props', function() {
|
||||
var markup = '<div class="foo" data-reactid=".rX"></div>';
|
||||
var output = Danger.dangerouslyRenderMarkup([markup])[0];
|
||||
|
||||
expect(output.nodeName).toBe('DIV');
|
||||
expect(output.className).toBe('foo');
|
||||
});
|
||||
|
||||
it('should render wrapped markup', function() {
|
||||
var markup = '<th data-reactid=".rX"></th>';
|
||||
var output = Danger.dangerouslyRenderMarkup([markup])[0];
|
||||
|
||||
expect(output.nodeName).toBe('TH');
|
||||
});
|
||||
|
||||
it('should render lists of markup with similar `nodeName`', function() {
|
||||
var renderedMarkup = Danger.dangerouslyRenderMarkup(
|
||||
['<p id="A">1</p>', '<p id="B">2</p>', '<p id="C">3</p>']
|
||||
);
|
||||
|
||||
expect(renderedMarkup.length).toBe(3);
|
||||
|
||||
expect(renderedMarkup[0].nodeName).toBe('P');
|
||||
expect(renderedMarkup[1].nodeName).toBe('P');
|
||||
expect(renderedMarkup[2].nodeName).toBe('P');
|
||||
|
||||
expect(renderedMarkup[0].innerHTML).toBe('1');
|
||||
expect(renderedMarkup[1].innerHTML).toBe('2');
|
||||
expect(renderedMarkup[2].innerHTML).toBe('3');
|
||||
});
|
||||
|
||||
it('should render lists of markup with different `nodeName`', function() {
|
||||
var renderedMarkup = Danger.dangerouslyRenderMarkup(
|
||||
['<p id="A">1</p>', '<td id="B">2</td>', '<p id="C">3</p>']
|
||||
);
|
||||
|
||||
expect(renderedMarkup.length).toBe(3);
|
||||
|
||||
expect(renderedMarkup[0].nodeName).toBe('P');
|
||||
expect(renderedMarkup[1].nodeName).toBe('TD');
|
||||
expect(renderedMarkup[2].nodeName).toBe('P');
|
||||
|
||||
expect(renderedMarkup[0].innerHTML).toBe('1');
|
||||
expect(renderedMarkup[1].innerHTML).toBe('2');
|
||||
expect(renderedMarkup[2].innerHTML).toBe('3');
|
||||
});
|
||||
|
||||
it('should throw when rendering invalid markup', function() {
|
||||
expect(function() {
|
||||
Danger.dangerouslyRenderMarkup(['']);
|
||||
}).toThrowError(
|
||||
'dangerouslyRenderMarkup(...): Missing markup.'
|
||||
);
|
||||
|
||||
spyOn(console, 'error');
|
||||
|
||||
var renderedMarkup = Danger.dangerouslyRenderMarkup(['<p></p><p></p>']);
|
||||
var args = console.error.calls.argsFor(0);
|
||||
|
||||
expect(renderedMarkup.length).toBe(1);
|
||||
expect(renderedMarkup[0].nodeName).toBe('P');
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
|
||||
expect(args.length).toBe(2);
|
||||
expect(args[0]).toBe('Danger: Discarding unexpected node:');
|
||||
expect(args[1].nodeName).toBe('P');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -811,8 +811,8 @@ describe('ReactDOMComponent', function() {
|
||||
expect(function() {
|
||||
ReactDOM.render(<input>children</input>, container);
|
||||
}).toThrowError(
|
||||
'input is a void element tag and must not have `children` or ' +
|
||||
'use `props.dangerouslySetInnerHTML`.'
|
||||
'input is a void element tag and must neither have `children` nor ' +
|
||||
'use `dangerouslySetInnerHTML`.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -825,8 +825,8 @@ describe('ReactDOMComponent', function() {
|
||||
container
|
||||
);
|
||||
}).toThrowError(
|
||||
'input is a void element tag and must not have `children` or use ' +
|
||||
'`props.dangerouslySetInnerHTML`.'
|
||||
'input is a void element tag and must neither have `children` nor use ' +
|
||||
'`dangerouslySetInnerHTML`.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -840,8 +840,8 @@ describe('ReactDOMComponent', function() {
|
||||
expect(function() {
|
||||
ReactDOM.render(<menu><menuitem>children</menuitem></menu>, container);
|
||||
}).toThrowError(
|
||||
'menuitem is a void element tag and must not have `children` or use ' +
|
||||
'`props.dangerouslySetInnerHTML`.'
|
||||
'menuitem is a void element tag and must neither have `children` nor use ' +
|
||||
'`dangerouslySetInnerHTML`.'
|
||||
);
|
||||
|
||||
});
|
||||
@@ -977,8 +977,8 @@ describe('ReactDOMComponent', function() {
|
||||
expect(function() {
|
||||
ReactDOM.render(<X />, container);
|
||||
}).toThrowError(
|
||||
'input is a void element tag and must not have `children` ' +
|
||||
'or use `props.dangerouslySetInnerHTML`. Check the render method of X.'
|
||||
'input is a void element tag and must neither have `children` ' +
|
||||
'nor use `dangerouslySetInnerHTML`. Check the render method of X.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1007,8 +1007,8 @@ describe('ReactDOMComponent', function() {
|
||||
expect(function() {
|
||||
ReactDOM.render(<input>children</input>, container);
|
||||
}).toThrowError(
|
||||
'input is a void element tag and must not have `children` or use ' +
|
||||
'`props.dangerouslySetInnerHTML`.'
|
||||
'input is a void element tag and must neither have `children` nor use ' +
|
||||
'`dangerouslySetInnerHTML`.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1021,8 +1021,8 @@ describe('ReactDOMComponent', function() {
|
||||
container
|
||||
);
|
||||
}).toThrowError(
|
||||
'input is a void element tag and must not have `children` or use ' +
|
||||
'`props.dangerouslySetInnerHTML`.'
|
||||
'input is a void element tag and must neither have `children` nor use ' +
|
||||
'`dangerouslySetInnerHTML`.'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDOMNullInputValuePropDevtool
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
|
||||
var warning = require('warning');
|
||||
|
||||
var didWarnValueNull = false;
|
||||
|
||||
function handleElement(debugID, element) {
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
if (element.type !== 'input' && element.type !== 'textarea' && element.type !== 'select') {
|
||||
return;
|
||||
}
|
||||
if (element.props != null && element.props.value === null && !didWarnValueNull) {
|
||||
warning(
|
||||
false,
|
||||
'`value` prop on `%s` should not be null. ' +
|
||||
'Consider using the empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.%s',
|
||||
element.type,
|
||||
ReactComponentTreeDevtool.getStackAddendumByID(debugID)
|
||||
);
|
||||
|
||||
didWarnValueNull = true;
|
||||
}
|
||||
}
|
||||
|
||||
var ReactDOMUnknownPropertyDevtool = {
|
||||
onBeforeMountComponent(debugID, element) {
|
||||
handleElement(debugID, element);
|
||||
},
|
||||
onBeforeUpdateComponent(debugID, element) {
|
||||
handleElement(debugID, element);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactDOMUnknownPropertyDevtool;
|
||||
@@ -118,7 +118,7 @@ var ReactNativeMount = {
|
||||
var prevWrappedElement = prevComponent._currentElement;
|
||||
var prevElement = prevWrappedElement.props;
|
||||
if (shouldUpdateReactComponent(prevElement, nextElement)) {
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement);
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement, emptyObject);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
|
||||
}
|
||||
@@ -135,19 +135,9 @@ var ReactNativeMount = {
|
||||
|
||||
ReactNativeTagHandles.assertRootTag(containerTag);
|
||||
|
||||
var instance = instantiateReactComponent(nextWrappedElement);
|
||||
var instance = instantiateReactComponent(nextWrappedElement, false);
|
||||
ReactNativeMount._instancesByContainerID[containerTag] = instance;
|
||||
|
||||
if (__DEV__) {
|
||||
// Mute future events from the top level wrapper.
|
||||
// It is an implementation detail that devtools should not know about.
|
||||
instance._debugID = 0;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
}
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
// according to the current batching strategy.
|
||||
@@ -162,7 +152,6 @@ var ReactNativeMount = {
|
||||
ReactInstrumentation.debugTool.onMountRootComponent(
|
||||
instance._renderedComponent._debugID
|
||||
);
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
var component = instance.getPublicInstance();
|
||||
if (callback) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var Transaction = require('Transaction');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
/**
|
||||
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
|
||||
@@ -42,6 +44,13 @@ var ON_DOM_READY_QUEUEING = {
|
||||
*/
|
||||
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];
|
||||
|
||||
if (__DEV__) {
|
||||
TRANSACTION_WRAPPERS.push({
|
||||
initialize: ReactInstrumentation.debugTool.onBeginFlush,
|
||||
close: ReactInstrumentation.debugTool.onEndFlush,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently:
|
||||
* - The order that these are listed in the transaction is critical:
|
||||
@@ -81,6 +90,13 @@ var Mixin = {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return ReactUpdateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for this, and will invoke this before allowing this
|
||||
* instance to be reused.
|
||||
|
||||
@@ -19,31 +19,101 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { HostChildren } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
|
||||
var scheduledHighPriCallback = null;
|
||||
var scheduledLowPriCallback = null;
|
||||
|
||||
const TERMINAL_TAG = 99;
|
||||
|
||||
type Container = { rootID: number, children: Array<Instance> };
|
||||
type Props = { };
|
||||
type Instance = { tag: 99, type: string, id: number, children: Array<Instance> };
|
||||
|
||||
var instanceCounter = 0;
|
||||
|
||||
function recursivelyAppendChildren(flatArray : Array<Instance>, child : HostChildren<Instance>) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
if (child.tag === TERMINAL_TAG) {
|
||||
flatArray.push(child);
|
||||
} else {
|
||||
let node = child;
|
||||
do {
|
||||
recursivelyAppendChildren(flatArray, node.output);
|
||||
} while (node = node.sibling);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenChildren(children : HostChildren<Instance>) {
|
||||
const flatArray = [];
|
||||
recursivelyAppendChildren(flatArray, children);
|
||||
return flatArray;
|
||||
}
|
||||
|
||||
var NoopRenderer = ReactFiberReconciler({
|
||||
|
||||
createHostInstance() {
|
||||
|
||||
updateContainer(containerInfo : Container, children : HostChildren<Instance>) : void {
|
||||
console.log('Update container #' + containerInfo.rootID);
|
||||
containerInfo.children = flattenChildren(children);
|
||||
},
|
||||
|
||||
createInstance(type : string, props : Props, children : HostChildren<Instance>) : Instance {
|
||||
console.log('Create instance #' + instanceCounter);
|
||||
const inst = {
|
||||
tag: TERMINAL_TAG,
|
||||
id: instanceCounter++,
|
||||
type: type,
|
||||
children: flattenChildren(children),
|
||||
};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
|
||||
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
|
||||
return inst;
|
||||
},
|
||||
|
||||
prepareUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : boolean {
|
||||
console.log('Prepare for update on #' + instance.id);
|
||||
return true;
|
||||
},
|
||||
|
||||
commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : void {
|
||||
console.log('Commit update on #' + instance.id);
|
||||
instance.children = flattenChildren(children);
|
||||
},
|
||||
|
||||
deleteInstance(instance : Instance) : void {
|
||||
console.log('Delete #' + instance.id);
|
||||
},
|
||||
|
||||
scheduleHighPriCallback(callback) {
|
||||
scheduledHighPriCallback = callback;
|
||||
},
|
||||
|
||||
scheduleLowPriCallback(callback) {
|
||||
scheduledLowPriCallback = callback;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var rootContainer = { rootID: 0, children: [] };
|
||||
|
||||
var root = null;
|
||||
|
||||
var ReactNoop = {
|
||||
|
||||
root: rootContainer,
|
||||
|
||||
render(element : ReactElement<any>) {
|
||||
|
||||
NoopRenderer.mountNewRoot(element);
|
||||
|
||||
if (!root) {
|
||||
root = NoopRenderer.mountContainer(element, rootContainer);
|
||||
} else {
|
||||
NoopRenderer.updateContainer(element, root);
|
||||
}
|
||||
},
|
||||
|
||||
flushHighPri() {
|
||||
@@ -79,6 +149,41 @@ var ReactNoop = {
|
||||
ReactNoop.flushLowPri();
|
||||
},
|
||||
|
||||
// Logs the current state of the tree.
|
||||
dumpTree() {
|
||||
if (!root) {
|
||||
console.log('Nothing rendered yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
function logHostInstances(children: Array<Instance>, depth) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
console.log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
|
||||
logHostInstances(child.children, depth + 1);
|
||||
}
|
||||
}
|
||||
function logContainer(container : Container, depth) {
|
||||
console.log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
logHostInstances(container.children, depth + 1);
|
||||
}
|
||||
|
||||
function logFiber(fiber : Fiber, depth) {
|
||||
console.log(' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'), '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']');
|
||||
if (fiber.child) {
|
||||
logFiber(fiber.child, depth + 1);
|
||||
}
|
||||
if (fiber.sibling) {
|
||||
logFiber(fiber.sibling, depth);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('HOST INSTANCES:');
|
||||
logContainer(rootContainer, 0);
|
||||
console.log('FIBERS:');
|
||||
logFiber((root.stateNode : any).current, 0);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactNoop;
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
|
||||
var ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
var performanceNow = require('performanceNow');
|
||||
@@ -20,23 +23,21 @@ var eventHandlers = [];
|
||||
var handlerDoesThrowForEvent = {};
|
||||
|
||||
function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) {
|
||||
if (__DEV__) {
|
||||
eventHandlers.forEach(function(handler) {
|
||||
try {
|
||||
if (handler[handlerFunctionName]) {
|
||||
handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
} catch (e) {
|
||||
warning(
|
||||
handlerDoesThrowForEvent[handlerFunctionName],
|
||||
'exception thrown by devtool while handling %s: %s',
|
||||
handlerFunctionName,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
handlerDoesThrowForEvent[handlerFunctionName] = true;
|
||||
eventHandlers.forEach(function(handler) {
|
||||
try {
|
||||
if (handler[handlerFunctionName]) {
|
||||
handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
warning(
|
||||
handlerDoesThrowForEvent[handlerFunctionName],
|
||||
'exception thrown by devtool while handling %s: %s',
|
||||
handlerFunctionName,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
handlerDoesThrowForEvent[handlerFunctionName] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var isProfiling = false;
|
||||
@@ -73,32 +74,30 @@ function getTreeSnapshot(registeredIDs) {
|
||||
}
|
||||
|
||||
function resetMeasurements() {
|
||||
if (__DEV__) {
|
||||
var previousStartTime = currentFlushStartTime;
|
||||
var previousMeasurements = currentFlushMeasurements || [];
|
||||
var previousOperations = ReactHostOperationHistoryDevtool.getHistory();
|
||||
|
||||
if (!isProfiling || currentFlushNesting === 0) {
|
||||
currentFlushStartTime = null;
|
||||
currentFlushMeasurements = null;
|
||||
clearHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousMeasurements.length || previousOperations.length) {
|
||||
var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs();
|
||||
flushHistory.push({
|
||||
duration: performanceNow() - previousStartTime,
|
||||
measurements: previousMeasurements || [],
|
||||
operations: previousOperations || [],
|
||||
treeSnapshot: getTreeSnapshot(registeredIDs),
|
||||
});
|
||||
}
|
||||
var previousStartTime = currentFlushStartTime;
|
||||
var previousMeasurements = currentFlushMeasurements || [];
|
||||
var previousOperations = ReactHostOperationHistoryDevtool.getHistory();
|
||||
|
||||
if (currentFlushNesting === 0) {
|
||||
currentFlushStartTime = null;
|
||||
currentFlushMeasurements = null;
|
||||
clearHistory();
|
||||
currentFlushStartTime = performanceNow();
|
||||
currentFlushMeasurements = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousMeasurements.length || previousOperations.length) {
|
||||
var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs();
|
||||
flushHistory.push({
|
||||
duration: performanceNow() - previousStartTime,
|
||||
measurements: previousMeasurements || [],
|
||||
operations: previousOperations || [],
|
||||
treeSnapshot: getTreeSnapshot(registeredIDs),
|
||||
});
|
||||
}
|
||||
|
||||
clearHistory();
|
||||
currentFlushStartTime = performanceNow();
|
||||
currentFlushMeasurements = [];
|
||||
}
|
||||
|
||||
function checkDebugID(debugID) {
|
||||
@@ -106,7 +105,7 @@ function checkDebugID(debugID) {
|
||||
}
|
||||
|
||||
function beginLifeCycleTimer(debugID, timerType) {
|
||||
if (!isProfiling || currentFlushNesting === 0) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
warning(
|
||||
@@ -125,7 +124,7 @@ function beginLifeCycleTimer(debugID, timerType) {
|
||||
}
|
||||
|
||||
function endLifeCycleTimer(debugID, timerType) {
|
||||
if (!isProfiling || currentFlushNesting === 0) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
warning(
|
||||
@@ -137,11 +136,13 @@ function endLifeCycleTimer(debugID, timerType) {
|
||||
currentTimerType || 'no',
|
||||
(debugID === currentTimerDebugID) ? 'the same' : 'another'
|
||||
);
|
||||
currentFlushMeasurements.push({
|
||||
timerType,
|
||||
instanceID: debugID,
|
||||
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
|
||||
});
|
||||
if (isProfiling) {
|
||||
currentFlushMeasurements.push({
|
||||
timerType,
|
||||
instanceID: debugID,
|
||||
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
|
||||
});
|
||||
}
|
||||
currentTimerStartTime = null;
|
||||
currentTimerNestedFlushDuration = null;
|
||||
currentTimerDebugID = null;
|
||||
@@ -187,57 +188,47 @@ var ReactDebugTool = {
|
||||
return isProfiling;
|
||||
},
|
||||
beginProfiling() {
|
||||
if (__DEV__) {
|
||||
if (isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = true;
|
||||
flushHistory.length = 0;
|
||||
resetMeasurements();
|
||||
if (isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = true;
|
||||
flushHistory.length = 0;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
|
||||
},
|
||||
endProfiling() {
|
||||
if (__DEV__) {
|
||||
if (!isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = false;
|
||||
resetMeasurements();
|
||||
if (!isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = false;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.removeDevtool(ReactHostOperationHistoryDevtool);
|
||||
},
|
||||
getFlushHistory() {
|
||||
return flushHistory;
|
||||
},
|
||||
onBeginFlush() {
|
||||
if (__DEV__) {
|
||||
currentFlushNesting++;
|
||||
resetMeasurements();
|
||||
pauseCurrentLifeCycleTimer();
|
||||
}
|
||||
currentFlushNesting++;
|
||||
resetMeasurements();
|
||||
pauseCurrentLifeCycleTimer();
|
||||
emitEvent('onBeginFlush');
|
||||
},
|
||||
onEndFlush() {
|
||||
if (__DEV__) {
|
||||
resetMeasurements();
|
||||
currentFlushNesting--;
|
||||
resumeCurrentLifeCycleTimer();
|
||||
}
|
||||
resetMeasurements();
|
||||
currentFlushNesting--;
|
||||
resumeCurrentLifeCycleTimer();
|
||||
emitEvent('onEndFlush');
|
||||
},
|
||||
onBeginLifeCycleTimer(debugID, timerType) {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
|
||||
if (__DEV__) {
|
||||
beginLifeCycleTimer(debugID, timerType);
|
||||
}
|
||||
beginLifeCycleTimer(debugID, timerType);
|
||||
},
|
||||
onEndLifeCycleTimer(debugID, timerType) {
|
||||
checkDebugID(debugID);
|
||||
if (__DEV__) {
|
||||
endLifeCycleTimer(debugID, timerType);
|
||||
}
|
||||
endLifeCycleTimer(debugID, timerType);
|
||||
emitEvent('onEndLifeCycleTimer', debugID, timerType);
|
||||
},
|
||||
onBeginReconcilerTimer(debugID, timerType) {
|
||||
@@ -248,6 +239,12 @@ var ReactDebugTool = {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onEndReconcilerTimer', debugID, timerType);
|
||||
},
|
||||
onError(debugID) {
|
||||
if (currentTimerDebugID != null) {
|
||||
endLifeCycleTimer(currentTimerDebugID, currentTimerType);
|
||||
}
|
||||
emitEvent('onError', debugID);
|
||||
},
|
||||
onBeginProcessingChildContext() {
|
||||
emitEvent('onBeginProcessingChildContext');
|
||||
},
|
||||
@@ -267,6 +264,7 @@ var ReactDebugTool = {
|
||||
},
|
||||
onSetChildren(debugID, childDebugIDs) {
|
||||
checkDebugID(debugID);
|
||||
childDebugIDs.forEach(checkDebugID);
|
||||
emitEvent('onSetChildren', debugID, childDebugIDs);
|
||||
},
|
||||
onSetOwner(debugID, ownerDebugID) {
|
||||
@@ -310,17 +308,11 @@ var ReactDebugTool = {
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
|
||||
var ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
|
||||
ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
|
||||
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
|
||||
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
|
||||
if ((/[?&]react_perf\b/).test(url)) {
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
|
||||
ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
|
||||
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
|
||||
if ((/[?&]react_perf\b/).test(url)) {
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
|
||||
module.exports = ReactDebugTool;
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var debugTool = null;
|
||||
|
||||
module.exports = {debugTool: ReactDebugTool};
|
||||
if (__DEV__) {
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
debugTool = ReactDebugTool;
|
||||
}
|
||||
|
||||
module.exports = {debugTool};
|
||||
@@ -467,5 +467,49 @@ describe('ReactPerf', function() {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
|
||||
__DEV__ = true;
|
||||
})
|
||||
});
|
||||
|
||||
it('should work when measurement starts during reconciliation', () => {
|
||||
// https://github.com/facebook/react/issues/6949#issuecomment-230371009
|
||||
var Measurer = React.createClass({
|
||||
componentWillMount() {
|
||||
ReactPerf.start();
|
||||
},
|
||||
componentDidMount() {
|
||||
ReactPerf.stop();
|
||||
},
|
||||
componentWillUpdate() {
|
||||
ReactPerf.start();
|
||||
},
|
||||
componentDidUpdate() {
|
||||
ReactPerf.stop();
|
||||
},
|
||||
render() {
|
||||
// Force reconciliation despite constant element
|
||||
return React.cloneElement(this.props.children);
|
||||
},
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Measurer><App /></Measurer>, container);
|
||||
expect(ReactPerf.getWasted()).toEqual([]);
|
||||
|
||||
ReactDOM.render(<Measurer><App /></Measurer>, container);
|
||||
expect(ReactPerf.getWasted()).toEqual([{
|
||||
key: 'Measurer',
|
||||
instanceCount: 1,
|
||||
inclusiveRenderDuration: 4,
|
||||
renderCount: 1,
|
||||
}, {
|
||||
key: 'App',
|
||||
instanceCount: 1,
|
||||
inclusiveRenderDuration: 3,
|
||||
renderCount: 1,
|
||||
}, {
|
||||
key: 'App > Box',
|
||||
instanceCount: 2,
|
||||
inclusiveRenderDuration: 2,
|
||||
renderCount: 2,
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -1716,103 +1716,137 @@ describe('ReactComponentTreeDevtool', () => {
|
||||
expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
|
||||
});
|
||||
|
||||
it('creates current stack addenda', () => {
|
||||
function getAddendum(element) {
|
||||
var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
|
||||
return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
|
||||
var Anon = React.createClass({displayName: null, render: () => null});
|
||||
var Orange = React.createClass({render: () => null});
|
||||
|
||||
expect(getAddendum()).toBe(
|
||||
''
|
||||
);
|
||||
expect(getAddendum(<div />)).toBe(
|
||||
'\n in div (at **)'
|
||||
);
|
||||
expect(getAddendum(<Anon />)).toBe(
|
||||
'\n in Unknown (at **)'
|
||||
);
|
||||
expect(getAddendum(<Orange />)).toBe(
|
||||
'\n in Orange (at **)'
|
||||
);
|
||||
expect(getAddendum(React.createElement(Orange))).toBe(
|
||||
'\n in Orange'
|
||||
);
|
||||
|
||||
var renders = 0;
|
||||
var rOwnedByQ;
|
||||
|
||||
function Q() {
|
||||
return (rOwnedByQ = React.createElement(R));
|
||||
}
|
||||
function R() {
|
||||
return <div><S /></div>;
|
||||
}
|
||||
class S extends React.Component {
|
||||
componentDidMount() {
|
||||
// Check that the parent path is still fetched when only S itself is on
|
||||
// the stack.
|
||||
this.forceUpdate();
|
||||
describe('stack addenda', () => {
|
||||
it('gets created', () => {
|
||||
function getAddendum(element) {
|
||||
var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
|
||||
return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
render() {
|
||||
expect(getAddendum()).toBe(
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
expect(getAddendum(<span />)).toBe(
|
||||
'\n in span (at **)' +
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
expect(getAddendum(React.createElement('span'))).toBe(
|
||||
'\n in span (created by S)' +
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
renders++;
|
||||
return null;
|
||||
|
||||
var Anon = React.createClass({displayName: null, render: () => null});
|
||||
var Orange = React.createClass({render: () => null});
|
||||
|
||||
expect(getAddendum()).toBe(
|
||||
''
|
||||
);
|
||||
expect(getAddendum(<div />)).toBe(
|
||||
'\n in div (at **)'
|
||||
);
|
||||
expect(getAddendum(<Anon />)).toBe(
|
||||
'\n in Unknown (at **)'
|
||||
);
|
||||
expect(getAddendum(<Orange />)).toBe(
|
||||
'\n in Orange (at **)'
|
||||
);
|
||||
expect(getAddendum(React.createElement(Orange))).toBe(
|
||||
'\n in Orange'
|
||||
);
|
||||
|
||||
var renders = 0;
|
||||
var rOwnedByQ;
|
||||
|
||||
function Q() {
|
||||
return (rOwnedByQ = React.createElement(R));
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Q />, document.createElement('div'));
|
||||
expect(renders).toBe(2);
|
||||
|
||||
// Make sure owner is fetched for the top element too.
|
||||
expect(getAddendum(rOwnedByQ)).toBe(
|
||||
'\n in R (created by Q)'
|
||||
);
|
||||
});
|
||||
|
||||
it('creates stack addenda by ID', () => {
|
||||
function getAddendum(id) {
|
||||
var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
|
||||
return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
|
||||
class Q extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
function R() {
|
||||
return <div><S /></div>;
|
||||
}
|
||||
}
|
||||
class S extends React.Component {
|
||||
componentDidMount() {
|
||||
// Check that the parent path is still fetched when only S itself is on
|
||||
// the stack.
|
||||
this.forceUpdate();
|
||||
}
|
||||
render() {
|
||||
expect(getAddendum()).toBe(
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
expect(getAddendum(<span />)).toBe(
|
||||
'\n in span (at **)' +
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
expect(getAddendum(React.createElement('span'))).toBe(
|
||||
'\n in span (created by S)' +
|
||||
'\n in S (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in R (created by Q)' +
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
renders++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Q />, document.createElement('div'));
|
||||
expect(renders).toBe(2);
|
||||
|
||||
var q = ReactDOM.render(<Q />, document.createElement('div'));
|
||||
expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
// Make sure owner is fetched for the top element too.
|
||||
expect(getAddendum(rOwnedByQ)).toBe(
|
||||
'\n in R (created by Q)'
|
||||
);
|
||||
});
|
||||
|
||||
spyOn(console, 'error');
|
||||
getAddendum(-17);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ReactComponentTreeDevtool: Missing React element for debugID ' +
|
||||
'-17 when building stack'
|
||||
);
|
||||
});
|
||||
it('can be retrieved by ID', () => {
|
||||
function getAddendum(id) {
|
||||
var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
|
||||
return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
|
||||
class Q extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var q = ReactDOM.render(<Q />, document.createElement('div'));
|
||||
expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
|
||||
'\n in Q (at **)'
|
||||
);
|
||||
|
||||
spyOn(console, 'error');
|
||||
getAddendum(-17);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ReactComponentTreeDevtool: Missing React element for ' +
|
||||
'debugID -17 when building stack'
|
||||
);
|
||||
});
|
||||
|
||||
it('is created during mounting', () => {
|
||||
// https://github.com/facebook/react/issues/7187
|
||||
var el = document.createElement('div');
|
||||
var portalEl = document.createElement('div');
|
||||
class Foo extends React.Component {
|
||||
componentWillMount() {
|
||||
ReactDOM.render(<div />, portalEl);
|
||||
}
|
||||
render() {
|
||||
return <div><div /></div>;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Foo />, el);
|
||||
});
|
||||
|
||||
it('is created when calling renderToString during render', () => {
|
||||
// https://github.com/facebook/react/issues/7190
|
||||
var el = document.createElement('div');
|
||||
class Foo extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{ReactDOMServer.renderToString(<div />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Foo />, el);
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
describe('ReactHostOperationHistoryDevtool', () => {
|
||||
var React;
|
||||
var ReactPerf;
|
||||
var ReactDOM;
|
||||
var ReactDOMComponentTree;
|
||||
var ReactDOMFeatureFlags;
|
||||
@@ -22,12 +23,19 @@ describe('ReactHostOperationHistoryDevtool', () => {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactPerf = require('ReactPerf');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');
|
||||
|
||||
ReactPerf.start();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ReactPerf.stop();
|
||||
})
|
||||
|
||||
function assertHistoryMatches(expectedHistory) {
|
||||
var actualHistory = ReactHostOperationHistoryDevtool.getHistory();
|
||||
expect(actualHistory).toEqual(expectedHistory);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
import type { ReactNodeList } from 'ReactTypes';
|
||||
|
||||
@@ -28,7 +29,7 @@ var {
|
||||
var ReactFiber = require('ReactFiber');
|
||||
var ReactReifiedYield = require('ReactReifiedYield');
|
||||
|
||||
function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
|
||||
function createSubsequentChild(returnFiber : Fiber, existingChild : ?Fiber, previousSibling : Fiber, newChildren, priority : PriorityLevel) : Fiber {
|
||||
if (typeof newChildren !== 'object' || newChildren === null) {
|
||||
return previousSibling;
|
||||
}
|
||||
@@ -41,34 +42,35 @@ function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousS
|
||||
element.key === existingChild.key) {
|
||||
// TODO: This is not sufficient since previous siblings could be new.
|
||||
// Will fix reconciliation properly later.
|
||||
const clone = ReactFiber.cloneFiber(existingChild);
|
||||
clone.input = element.props;
|
||||
const clone = ReactFiber.cloneFiber(existingChild, priority);
|
||||
clone.pendingProps = element.props;
|
||||
clone.child = existingChild.child;
|
||||
clone.sibling = null;
|
||||
clone.return = returnFiber;
|
||||
previousSibling.sibling = clone;
|
||||
return clone;
|
||||
}
|
||||
const child = ReactFiber.createFiberFromElement(element);
|
||||
const child = ReactFiber.createFiberFromElement(element, priority);
|
||||
previousSibling.sibling = child;
|
||||
child.parent = parent;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_COROUTINE_TYPE: {
|
||||
const coroutine = (newChildren : ReactCoroutine);
|
||||
const child = ReactFiber.createFiberFromCoroutine(coroutine);
|
||||
const child = ReactFiber.createFiberFromCoroutine(coroutine, priority);
|
||||
previousSibling.sibling = child;
|
||||
child.parent = parent;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_YIELD_TYPE: {
|
||||
const yieldNode = (newChildren : ReactYield);
|
||||
const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode);
|
||||
const child = ReactFiber.createFiberFromYield(yieldNode);
|
||||
const child = ReactFiber.createFiberFromYield(yieldNode, priority);
|
||||
child.output = reifiedYield;
|
||||
previousSibling.sibling = child;
|
||||
child.parent = parent;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +79,7 @@ function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousS
|
||||
let prev : Fiber = previousSibling;
|
||||
let existing : ?Fiber = existingChild;
|
||||
for (var i = 0; i < newChildren.length; i++) {
|
||||
prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
|
||||
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||||
if (prev && existing) {
|
||||
// TODO: This is not correct because there could've been more
|
||||
// than one sibling consumed but I don't want to return a tuple.
|
||||
@@ -91,7 +93,7 @@ function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousS
|
||||
}
|
||||
}
|
||||
|
||||
function createFirstChild(parent, existingChild, newChildren) {
|
||||
function createFirstChild(returnFiber, existingChild, newChildren, priority) {
|
||||
if (typeof newChildren !== 'object' || newChildren === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -103,21 +105,22 @@ function createFirstChild(parent, existingChild, newChildren) {
|
||||
element.type === existingChild.type &&
|
||||
element.key === existingChild.key) {
|
||||
// Get the clone of the existing fiber.
|
||||
const clone = ReactFiber.cloneFiber(existingChild);
|
||||
clone.input = element.props;
|
||||
const clone = ReactFiber.cloneFiber(existingChild, priority);
|
||||
clone.pendingProps = element.props;
|
||||
clone.child = existingChild.child;
|
||||
clone.sibling = null;
|
||||
clone.return = returnFiber;
|
||||
return clone;
|
||||
}
|
||||
const child = ReactFiber.createFiberFromElement(element);
|
||||
child.parent = parent;
|
||||
const child = ReactFiber.createFiberFromElement(element, priority);
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_COROUTINE_TYPE: {
|
||||
const coroutine = (newChildren : ReactCoroutine);
|
||||
const child = ReactFiber.createFiberFromCoroutine(coroutine);
|
||||
child.parent = parent;
|
||||
const child = ReactFiber.createFiberFromCoroutine(coroutine, priority);
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -127,9 +130,9 @@ function createFirstChild(parent, existingChild, newChildren) {
|
||||
// the fragment.
|
||||
const yieldNode = (newChildren : ReactYield);
|
||||
const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode);
|
||||
const child = ReactFiber.createFiberFromYield(yieldNode);
|
||||
const child = ReactFiber.createFiberFromYield(yieldNode, priority);
|
||||
child.output = reifiedYield;
|
||||
child.parent = parent;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -140,10 +143,10 @@ function createFirstChild(parent, existingChild, newChildren) {
|
||||
var existing : ?Fiber = existingChild;
|
||||
for (var i = 0; i < newChildren.length; i++) {
|
||||
if (prev == null) {
|
||||
prev = createFirstChild(parent, existing, newChildren[i]);
|
||||
prev = createFirstChild(returnFiber, existing, newChildren[i], priority);
|
||||
first = prev;
|
||||
} else {
|
||||
prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
|
||||
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||||
}
|
||||
if (prev && existing) {
|
||||
// TODO: This is not correct because there could've been more
|
||||
@@ -158,6 +161,8 @@ function createFirstChild(parent, existingChild, newChildren) {
|
||||
}
|
||||
}
|
||||
|
||||
exports.reconcileChildFibers = function(parent : Fiber, currentFirstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
|
||||
return createFirstChild(parent, currentFirstChild, newChildren);
|
||||
// TODO: This API won't work because we'll need to transfer the side-effects of
|
||||
// unmounting children to the returnFiber.
|
||||
exports.reconcileChildFibers = function(returnFiber : Fiber, currentFirstChild : ?Fiber, newChildren : ReactNodeList, priority : PriorityLevel) : ?Fiber {
|
||||
return createFirstChild(returnFiber, currentFirstChild, newChildren, priority);
|
||||
};
|
||||
@@ -12,18 +12,25 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactTypesOfWork = require('ReactTypesOfWork');
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
import type { TypeOfWork } from 'ReactTypeOfWork';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
YieldComponent,
|
||||
} = ReactTypesOfWork;
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
var {
|
||||
NoWork,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
// An Instance is shared between all versions of a component. We can easily
|
||||
// break this out into a separate object to avoid copying so much to the
|
||||
@@ -32,12 +39,7 @@ import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
type Instance = {
|
||||
|
||||
// Tag identifying the type of fiber.
|
||||
tag: number,
|
||||
|
||||
// The parent Fiber used to create this one. The type is constrained to the
|
||||
// Instance part of the Fiber since it is not safe to traverse the tree from
|
||||
// the instance.
|
||||
parent: ?Instance, // Consider a regenerated temporary parent stack instead.
|
||||
tag: TypeOfWork,
|
||||
|
||||
// Unique identifier of this child.
|
||||
key: null | string,
|
||||
@@ -46,7 +48,11 @@ type Instance = {
|
||||
type: any,
|
||||
|
||||
// The local state associated with this fiber.
|
||||
stateNode: ?Object,
|
||||
stateNode: any,
|
||||
|
||||
// Conceptual aliases
|
||||
// parent : Instance -> return The parent happens to be the same as the
|
||||
// return fiber since we've merged the fiber and instance.
|
||||
|
||||
};
|
||||
|
||||
@@ -54,6 +60,12 @@ type Instance = {
|
||||
// be more than one per component.
|
||||
export type Fiber = Instance & {
|
||||
|
||||
// The Fiber to return to after finishing processing this one.
|
||||
// This is effectively the parent, but there can be multiple parents (two)
|
||||
// so this is only the parent of the thing we're currently processing.
|
||||
// It is conceptually the same as the return address of a stack frame.
|
||||
return: ?Fiber,
|
||||
|
||||
// Singly Linked List Tree Structure.
|
||||
child: ?Fiber,
|
||||
sibling: ?Fiber,
|
||||
@@ -63,32 +75,51 @@ export type Fiber = Instance & {
|
||||
ref: null | (handle : ?Object) => void,
|
||||
|
||||
// Input is the data coming into process this fiber. Arguments. Props.
|
||||
input: any, // This type will be more specific once we overload the tag.
|
||||
// TODO: I think that there is a way to merge input and memoizedInput somehow.
|
||||
memoizedInput: any, // The input used to create the output.
|
||||
pendingProps: any, // This type will be more specific once we overload the tag.
|
||||
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
|
||||
memoizedProps: any, // The props used to create the output.
|
||||
// Output is the return value of this fiber, or a linked list of return values
|
||||
// if this returns multiple values. Such as a fragment.
|
||||
output: any, // This type will be more specific once we overload the tag.
|
||||
|
||||
// Singly linked list fast path to the next fiber with side-effects.
|
||||
nextEffect: ?Fiber,
|
||||
|
||||
// The first and last fiber with side-effect within this subtree. This allows
|
||||
// us to reuse a slice of the linked list when we reuse the work done within
|
||||
// this fiber.
|
||||
firstEffect: ?Fiber,
|
||||
lastEffect: ?Fiber,
|
||||
|
||||
|
||||
// This will be used to quickly determine if a subtree has no pending changes.
|
||||
hasPendingChanges: bool,
|
||||
pendingWorkPriority: PriorityLevel,
|
||||
|
||||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||||
// memory if we need to.
|
||||
alternate: ?Fiber,
|
||||
|
||||
// Keeps track if we've completed this node or if we're currently in the
|
||||
// middle of processing it. We really should know this based on pendingProps
|
||||
// or something else. We could also reuse the tag for this purpose. However,
|
||||
// I'm not really sure so I'll use a flag for now.
|
||||
// TODO: Find another way to infer this flag.
|
||||
hasWorkInProgress: bool,
|
||||
|
||||
// Conceptual aliases
|
||||
// workInProgress : Fiber -> alternate The alternate used for reuse happens
|
||||
// to be the same as work in progress.
|
||||
|
||||
};
|
||||
|
||||
var createFiber = function(tag : number, key : null | string) : Fiber {
|
||||
var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
|
||||
return {
|
||||
|
||||
// Instance
|
||||
|
||||
tag: tag,
|
||||
|
||||
parent: null,
|
||||
|
||||
key: key,
|
||||
|
||||
type: null,
|
||||
@@ -97,16 +128,24 @@ var createFiber = function(tag : number, key : null | string) : Fiber {
|
||||
|
||||
// Fiber
|
||||
|
||||
return: null,
|
||||
|
||||
child: null,
|
||||
sibling: null,
|
||||
|
||||
ref: null,
|
||||
|
||||
input: null,
|
||||
memoizedInput: null,
|
||||
pendingProps: null,
|
||||
memoizedProps: null,
|
||||
output: null,
|
||||
|
||||
hasPendingChanges: true,
|
||||
nextEffect: null,
|
||||
firstEffect: null,
|
||||
lastEffect: null,
|
||||
|
||||
pendingWorkPriority: NoWork,
|
||||
|
||||
hasWorkInProgress: false,
|
||||
|
||||
alternate: null,
|
||||
|
||||
@@ -118,35 +157,53 @@ function shouldConstruct(Component) {
|
||||
}
|
||||
|
||||
// This is used to create an alternate fiber to do work on.
|
||||
exports.cloneFiber = function(fiber : Fiber) : Fiber {
|
||||
exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fiber {
|
||||
// We use a double buffering pooling technique because we know that we'll only
|
||||
// ever need at most two versions of a tree. We pool the "other" unused node
|
||||
// that we're free to reuse. This is lazily created to avoid allocating extra
|
||||
// objects for things that are never updated. It also allow us to reclaim the
|
||||
// extra memory if needed.
|
||||
if (fiber.alternate) {
|
||||
return fiber.alternate;
|
||||
let alt = fiber.alternate;
|
||||
if (alt) {
|
||||
alt.stateNode = fiber.stateNode;
|
||||
alt.child = fiber.child;
|
||||
alt.sibling = fiber.sibling;
|
||||
alt.ref = alt.ref;
|
||||
alt.pendingProps = fiber.pendingProps;
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
// Whenever we clone, we do so to get a new work in progress.
|
||||
// This ensures that we've reset these in the new tree.
|
||||
alt.nextEffect = null;
|
||||
alt.firstEffect = null;
|
||||
alt.lastEffect = null;
|
||||
|
||||
return alt;
|
||||
}
|
||||
|
||||
// This should not have an alternate already
|
||||
var alt = createFiber(fiber.tag, fiber.key);
|
||||
|
||||
if (fiber.parent) {
|
||||
// TODO: This assumes the parent's alternate is already created.
|
||||
// Stop using the alternates of parents once we have a parent stack.
|
||||
// $FlowFixMe: This downcast is not safe. It is intentionally an error.
|
||||
alt.parent = fiber.parent.alternate;
|
||||
}
|
||||
|
||||
alt = createFiber(fiber.tag, fiber.key);
|
||||
alt.type = fiber.type;
|
||||
alt.stateNode = fiber.stateNode;
|
||||
alt.child = fiber.child;
|
||||
alt.sibling = fiber.sibling;
|
||||
alt.ref = alt.ref;
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
alt.alternate = fiber;
|
||||
fiber.alternate = alt;
|
||||
return alt;
|
||||
};
|
||||
|
||||
exports.createFiberFromElement = function(element : ReactElement) {
|
||||
exports.createHostContainerFiber = function() {
|
||||
const fiber = createFiber(HostContainer, null);
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromElement = function(element : ReactElement, priorityLevel : PriorityLevel) {
|
||||
const fiber = exports.createFiberFromElementType(element.type, element.key);
|
||||
fiber.input = element.props;
|
||||
fiber.pendingProps = element.props;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
@@ -169,14 +226,16 @@ exports.createFiberFromElementType = function(type : mixed, key : null | string)
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine) {
|
||||
exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priorityLevel : PriorityLevel) {
|
||||
const fiber = createFiber(CoroutineComponent, coroutine.key);
|
||||
fiber.type = coroutine.handler;
|
||||
fiber.input = coroutine;
|
||||
fiber.pendingProps = coroutine;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromYield = function(yieldNode : ReactYield) {
|
||||
exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) {
|
||||
const fiber = createFiber(YieldComponent, yieldNode.key);
|
||||
fiber.pendingProps = {};
|
||||
return fiber;
|
||||
};
|
||||
@@ -14,127 +14,237 @@
|
||||
|
||||
import type { ReactCoroutine } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactChildFiber = require('ReactChildFiber');
|
||||
var ReactTypesOfWork = require('ReactTypesOfWork');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
} = ReactTypesOfWork;
|
||||
} = ReactTypeOfWork;
|
||||
var {
|
||||
NoWork,
|
||||
OffscreenPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork');
|
||||
|
||||
function reconcileChildren(current, workInProgress, nextChildren) {
|
||||
workInProgress.child = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
current ? current.child : null,
|
||||
nextChildren
|
||||
);
|
||||
}
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.input;
|
||||
console.log('update fn:', fn.name);
|
||||
var nextChildren = fn(props);
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
}
|
||||
|
||||
function updateHostComponent(current, workInProgress) {
|
||||
console.log('host component', workInProgress.type, typeof workInProgress.input.children === 'string' ? workInProgress.input.children : '');
|
||||
|
||||
var nextChildren = workInProgress.input.children;
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
}
|
||||
|
||||
function mountIndeterminateComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.input;
|
||||
var value = fn(props);
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
console.log('performed work on class:', fn.name);
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
} else {
|
||||
console.log('performed work on fn:', fn.name);
|
||||
// Proceed under the assumption that this is a functional component
|
||||
workInProgress.tag = FunctionalComponent;
|
||||
function reconcileChildren(current, workInProgress, nextChildren) {
|
||||
const priority = workInProgress.pendingWorkPriority;
|
||||
workInProgress.child = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
current ? current.child : null,
|
||||
nextChildren,
|
||||
priority
|
||||
);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
}
|
||||
|
||||
function updateCoroutineComponent(current, workInProgress) {
|
||||
var coroutine = (workInProgress.input : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
console.log('update fn:', fn.name);
|
||||
var nextChildren = fn(props);
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
}
|
||||
console.log('begin coroutine', workInProgress.type.name);
|
||||
reconcileChildren(current, workInProgress, coroutine.children);
|
||||
}
|
||||
|
||||
function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
if (current && workInProgress.input === current.memoizedInput) {
|
||||
// The most likely scenario is that the previous copy of the tree contains
|
||||
// the same input as the new one. In that case, we can just copy the output
|
||||
// and children from that node.
|
||||
workInProgress.output = current.output;
|
||||
workInProgress.child = current.child;
|
||||
workInProgress.stateNode = current.stateNode;
|
||||
return null;
|
||||
}
|
||||
if (workInProgress.input === workInProgress.memoizedInput) {
|
||||
// In a ping-pong scenario, this version could actually contain the
|
||||
// old input. In that case, we can just bail out.
|
||||
return null;
|
||||
}
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
mountIndeterminateComponent(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
case FunctionalComponent:
|
||||
updateFunctionalComponent(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
case ClassComponent:
|
||||
console.log('class component', workInProgress.input.type.name);
|
||||
return workInProgress.child;
|
||||
case HostComponent:
|
||||
updateHostComponent(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
case CoroutineHandlerPhase:
|
||||
// This is a restart. Reset the tag to the initial phase.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
// Intentionally fall through since this is now the same.
|
||||
case CoroutineComponent:
|
||||
updateCoroutineComponent(current, workInProgress);
|
||||
// This doesn't take arbitrary time so we could synchronously just begin
|
||||
// eagerly do the work of workInProgress.child as an optimization.
|
||||
if (workInProgress.child) {
|
||||
return beginWork(
|
||||
workInProgress.child.alternate,
|
||||
workInProgress.child
|
||||
);
|
||||
}
|
||||
return workInProgress.child;
|
||||
case YieldComponent:
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
if (workInProgress.sibling) {
|
||||
return beginWork(
|
||||
workInProgress.sibling.alternate,
|
||||
workInProgress.sibling
|
||||
);
|
||||
}
|
||||
function updateHostComponent(current, workInProgress) {
|
||||
console.log('host component', workInProgress.type, typeof workInProgress.pendingProps.children === 'string' ? workInProgress.pendingProps.children : '');
|
||||
|
||||
var nextChildren = workInProgress.pendingProps.children;
|
||||
|
||||
let priority = workInProgress.pendingWorkPriority;
|
||||
if (workInProgress.pendingProps.hidden && priority !== OffscreenPriority) {
|
||||
// If this host component is hidden, we can reconcile its children at
|
||||
// the lowest priority and bail out from this particular pass. Unless, we're
|
||||
// currently reconciling the lowest priority.
|
||||
workInProgress.child = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
current ? current.child : null,
|
||||
nextChildren,
|
||||
OffscreenPriority
|
||||
);
|
||||
workInProgress.pendingWorkPriority = OffscreenPriority;
|
||||
return null;
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
} else {
|
||||
workInProgress.child = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
current ? current.child : null,
|
||||
nextChildren,
|
||||
priority
|
||||
);
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.beginWork = beginWork;
|
||||
function mountIndeterminateComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var value = fn(props);
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
console.log('performed work on class:', fn.name);
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
if (workInProgress.alternate) {
|
||||
workInProgress.alternate.tag = ClassComponent;
|
||||
}
|
||||
} else {
|
||||
console.log('performed work on fn:', fn.name);
|
||||
// Proceed under the assumption that this is a functional component
|
||||
workInProgress.tag = FunctionalComponent;
|
||||
if (workInProgress.alternate) {
|
||||
workInProgress.alternate.tag = FunctionalComponent;
|
||||
}
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
}
|
||||
|
||||
function updateCoroutineComponent(current, workInProgress) {
|
||||
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
}
|
||||
console.log('begin coroutine', workInProgress.type.name);
|
||||
reconcileChildren(current, workInProgress, coroutine.children);
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
}
|
||||
|
||||
function reuseChildren(returnFiber : Fiber, firstChild : Fiber) {
|
||||
// TODO: None of this should be necessary if structured better.
|
||||
// The returnFiber pointer only needs to be updated when we walk into this child
|
||||
// which we don't do right now. If the pending work priority indicated only
|
||||
// if a child has work rather than if the node has work, then we would know
|
||||
// by a single lookup on workInProgress rather than having to go through
|
||||
// each child.
|
||||
let child = firstChild;
|
||||
do {
|
||||
// Update the returnFiber of the child to the newest fiber.
|
||||
child.return = returnFiber;
|
||||
// Retain the priority if there's any work left to do in the children.
|
||||
if (child.pendingWorkPriority !== NoWork &&
|
||||
(returnFiber.pendingWorkPriority === NoWork ||
|
||||
returnFiber.pendingWorkPriority > child.pendingWorkPriority)) {
|
||||
returnFiber.pendingWorkPriority = child.pendingWorkPriority;
|
||||
}
|
||||
} while (child = child.sibling);
|
||||
}
|
||||
|
||||
function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
if (current && workInProgress.pendingProps === current.memoizedProps) {
|
||||
// The most likely scenario is that the previous copy of the tree contains
|
||||
// the same props as the new one. In that case, we can just copy the output
|
||||
// and children from that node.
|
||||
workInProgress.memoizedProps = workInProgress.pendingProps;
|
||||
workInProgress.output = current.output;
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
workInProgress.pendingProps = null;
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
workInProgress.stateNode = current.stateNode;
|
||||
if (current.child) {
|
||||
// If we bail out but still has work with the current priority in this
|
||||
// subtree, we need to go find it right now. If we don't, we won't flush
|
||||
// it until the next tick.
|
||||
workInProgress.child = current.child;
|
||||
reuseChildren(workInProgress, workInProgress.child);
|
||||
if (workInProgress.pendingWorkPriority <= priorityLevel) {
|
||||
// TODO: This passes the current node and reads the priority level and
|
||||
// pending props from that. We want it to read our priority level and
|
||||
// pending props from the work in progress. Needs restructuring.
|
||||
return findNextUnitOfWorkAtPriority(workInProgress.alternate, priorityLevel);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
workInProgress.child = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!workInProgress.hasWorkInProgress &&
|
||||
workInProgress.pendingProps === workInProgress.memoizedProps &&
|
||||
workInProgress.pendingWorkPriority === NoWork) {
|
||||
// If we started this work before, and finished it, or if we're in a
|
||||
// ping-pong update scenario, this version could already be what we're
|
||||
// looking for. In that case, we should be able to just bail out.
|
||||
workInProgress.pendingProps = null;
|
||||
// TODO: We should be able to bail out if there is remaining work at a lower
|
||||
// priority too. However, I don't know if that is safe or even better since
|
||||
// the other tree could've potentially finished that work.
|
||||
return null;
|
||||
}
|
||||
|
||||
workInProgress.hasWorkInProgress = true;
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
mountIndeterminateComponent(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
case FunctionalComponent:
|
||||
updateFunctionalComponent(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
case ClassComponent:
|
||||
console.log('class component', workInProgress.pendingProps.type.name);
|
||||
return workInProgress.child;
|
||||
case HostContainer:
|
||||
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
if (workInProgress.child) {
|
||||
return beginWork(
|
||||
workInProgress.child.alternate,
|
||||
workInProgress.child
|
||||
);
|
||||
}
|
||||
return null;
|
||||
case HostComponent:
|
||||
return updateHostComponent(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
// This is a restart. Reset the tag to the initial phase.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
// Intentionally fall through since this is now the same.
|
||||
case CoroutineComponent:
|
||||
updateCoroutineComponent(current, workInProgress);
|
||||
// This doesn't take arbitrary time so we could synchronously just begin
|
||||
// eagerly do the work of workInProgress.child as an optimization.
|
||||
if (workInProgress.child) {
|
||||
return beginWork(
|
||||
workInProgress.child.alternate,
|
||||
workInProgress.child
|
||||
);
|
||||
}
|
||||
return workInProgress.child;
|
||||
case YieldComponent:
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
workInProgress.pendingWorkPriority = NoWork;
|
||||
if (workInProgress.sibling) {
|
||||
return beginWork(
|
||||
workInProgress.sibling.alternate,
|
||||
workInProgress.sibling
|
||||
);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
beginWork,
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberCommitWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
const updateContainer = config.updateContainer;
|
||||
const commitUpdate = config.commitUpdate;
|
||||
|
||||
function commitWork(finishedWork : Fiber) : void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
// TODO: Fire componentDidMount/componentDidUpdate, update refs
|
||||
return;
|
||||
}
|
||||
case HostContainer: {
|
||||
// TODO: Attach children to root container.
|
||||
const children = finishedWork.output;
|
||||
const root : FiberRoot = finishedWork.stateNode;
|
||||
const containerInfo : C = root.containerInfo;
|
||||
updateContainer(containerInfo, children);
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
if (finishedWork.stateNode == null || !finishedWork.alternate) {
|
||||
throw new Error('This should only be done during updates.');
|
||||
}
|
||||
// Commit the work prepared earlier.
|
||||
const child = (finishedWork.child : ?Fiber);
|
||||
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
|
||||
const newProps = finishedWork.memoizedProps;
|
||||
const current = finishedWork.alternate;
|
||||
const oldProps = current.memoizedProps;
|
||||
const instance : I = finishedWork.stateNode;
|
||||
commitUpdate(instance, oldProps, newProps, children);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new Error('This unit of work tag should not have side-effects.');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
commitWork,
|
||||
};
|
||||
|
||||
};
|
||||
@@ -14,112 +14,178 @@
|
||||
|
||||
import type { ReactCoroutine } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { ReifiedYield } from 'ReactReifiedYield';
|
||||
|
||||
var ReactChildFiber = require('ReactChildFiber');
|
||||
var ReactTypesOfWork = require('ReactTypesOfWork');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
} = ReactTypesOfWork;
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
function transferOutput(child : ?Fiber, parent : Fiber) {
|
||||
// If we have a single result, we just pass that through as the output to
|
||||
// avoid unnecessary traversal. When we have multiple output, we just pass
|
||||
// the linked list of fibers that has the individual output values.
|
||||
parent.output = (child && !child.sibling) ? child.output : child;
|
||||
parent.memoizedInput = parent.input;
|
||||
}
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
|
||||
if (!output) {
|
||||
// Ignore nulls etc.
|
||||
} else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
|
||||
// Detect if this is a fiber, if so it is a fragment result.
|
||||
// $FlowFixMe: Refinement issue.
|
||||
var item = (output : Fiber);
|
||||
do {
|
||||
recursivelyFillYields(yields, item.output);
|
||||
item = item.sibling;
|
||||
} while (item);
|
||||
} else {
|
||||
// $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
|
||||
yields.push(output);
|
||||
}
|
||||
}
|
||||
const createInstance = config.createInstance;
|
||||
const prepareUpdate = config.prepareUpdate;
|
||||
|
||||
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
|
||||
var coroutine = (workInProgress.input : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
function markForPreEffect(workInProgress : Fiber) {
|
||||
// Schedule a side-effect on this fiber, BEFORE the children's side-effects.
|
||||
if (workInProgress.firstEffect) {
|
||||
workInProgress.nextEffect = workInProgress.firstEffect;
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
} else {
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
workInProgress.lastEffect = workInProgress;
|
||||
}
|
||||
}
|
||||
|
||||
// First step of the coroutine has completed. Now we need to do the second.
|
||||
// TODO: It would be nice to have a multi stage coroutine represented by a
|
||||
// single component, or at least tail call optimize nested ones. Currently
|
||||
// that requires additional fields that we don't want to add to the fiber.
|
||||
// So this requires nested handlers.
|
||||
// Note: This doesn't mutate the alternate node. I don't think it needs to
|
||||
// since this stage is reset for every pass.
|
||||
workInProgress.tag = CoroutineHandlerPhase;
|
||||
|
||||
// Build up the yields.
|
||||
// TODO: Compare this to a generator or opaque helpers like Children.
|
||||
var yields : Array<ReifiedYield> = [];
|
||||
var child = workInProgress.child;
|
||||
while (child) {
|
||||
recursivelyFillYields(yields, child.output);
|
||||
child = child.sibling;
|
||||
/*
|
||||
function markForPostEffect(workInProgress : Fiber) {
|
||||
// Schedule a side-effect on this fiber, AFTER the children's side-effects.
|
||||
if (workInProgress.lastEffect) {
|
||||
workInProgress.lastEffect.nextEffect = workInProgress;
|
||||
} else {
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
}
|
||||
workInProgress.lastEffect = workInProgress;
|
||||
}
|
||||
var fn = coroutine.handler;
|
||||
var props = coroutine.props;
|
||||
var nextChildren = fn(props, yields);
|
||||
*/
|
||||
|
||||
var currentFirstChild = current ? current.stateNode : null;
|
||||
workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentFirstChild,
|
||||
nextChildren
|
||||
);
|
||||
return workInProgress.stateNode;
|
||||
}
|
||||
|
||||
exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
switch (workInProgress.tag) {
|
||||
case FunctionalComponent:
|
||||
console.log('/functional component', workInProgress.type.name);
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
break;
|
||||
case ClassComponent:
|
||||
console.log('/class component', workInProgress.type.name);
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
break;
|
||||
case HostComponent:
|
||||
console.log('/host component', workInProgress.type);
|
||||
break;
|
||||
case CoroutineComponent:
|
||||
console.log('/coroutine component', workInProgress.input.handler.name);
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
transferOutput(workInProgress.stateNode, workInProgress);
|
||||
// Reset the tag to now be a first phase coroutine.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
break;
|
||||
case YieldComponent:
|
||||
// Does nothing.
|
||||
break;
|
||||
|
||||
// Error cases
|
||||
case IndeterminateComponent:
|
||||
throw new Error('An indeterminate component should have become determinate before completing.');
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
|
||||
// If we have a single result, we just pass that through as the output to
|
||||
// avoid unnecessary traversal. When we have multiple output, we just pass
|
||||
// the linked list of fibers that has the individual output values.
|
||||
returnFiber.output = (child && !child.sibling) ? child.output : child;
|
||||
returnFiber.memoizedProps = returnFiber.pendingProps;
|
||||
}
|
||||
return null;
|
||||
|
||||
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
|
||||
if (!output) {
|
||||
// Ignore nulls etc.
|
||||
} else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
|
||||
// Detect if this is a fiber, if so it is a fragment result.
|
||||
// $FlowFixMe: Refinement issue.
|
||||
var item = (output : Fiber);
|
||||
do {
|
||||
recursivelyFillYields(yields, item.output);
|
||||
item = item.sibling;
|
||||
} while (item);
|
||||
} else {
|
||||
// $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
|
||||
yields.push(output);
|
||||
}
|
||||
}
|
||||
|
||||
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
|
||||
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
}
|
||||
|
||||
// First step of the coroutine has completed. Now we need to do the second.
|
||||
// TODO: It would be nice to have a multi stage coroutine represented by a
|
||||
// single component, or at least tail call optimize nested ones. Currently
|
||||
// that requires additional fields that we don't want to add to the fiber.
|
||||
// So this requires nested handlers.
|
||||
// Note: This doesn't mutate the alternate node. I don't think it needs to
|
||||
// since this stage is reset for every pass.
|
||||
workInProgress.tag = CoroutineHandlerPhase;
|
||||
|
||||
// Build up the yields.
|
||||
// TODO: Compare this to a generator or opaque helpers like Children.
|
||||
var yields : Array<ReifiedYield> = [];
|
||||
var child = workInProgress.child;
|
||||
while (child) {
|
||||
recursivelyFillYields(yields, child.output);
|
||||
child = child.sibling;
|
||||
}
|
||||
var fn = coroutine.handler;
|
||||
var props = coroutine.props;
|
||||
var nextChildren = fn(props, yields);
|
||||
|
||||
var currentFirstChild = current ? current.stateNode : null;
|
||||
// Inherit the priority of the returnFiber.
|
||||
const priority = workInProgress.pendingWorkPriority;
|
||||
workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentFirstChild,
|
||||
nextChildren,
|
||||
priority
|
||||
);
|
||||
return workInProgress.stateNode;
|
||||
}
|
||||
|
||||
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
switch (workInProgress.tag) {
|
||||
case FunctionalComponent:
|
||||
console.log('/functional component', workInProgress.type.name);
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
return null;
|
||||
case ClassComponent:
|
||||
console.log('/class component', workInProgress.type.name);
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
return null;
|
||||
case HostContainer:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// We don't know if a container has updated any children so we always
|
||||
// need to update it right now. We schedule this side-effect before
|
||||
// all the other side-effects in the subtree. We need to schedule it
|
||||
// before so that the entire tree is up-to-date before the life-cycles
|
||||
// are invoked.
|
||||
markForPreEffect(workInProgress);
|
||||
return null;
|
||||
case HostComponent:
|
||||
console.log('/host component', workInProgress.type);
|
||||
const child = workInProgress.child;
|
||||
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
|
||||
const newProps = workInProgress.pendingProps;
|
||||
workInProgress.memoizedProps = newProps;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
// If we have an alternate, that means this is an update and we need to
|
||||
// schedule a side-effect to do the updates.
|
||||
const oldProps = current.memoizedProps;
|
||||
const instance : I = workInProgress.stateNode;
|
||||
if (prepareUpdate(instance, oldProps, newProps, children)) {
|
||||
// This returns true if there was something to update.
|
||||
markForPreEffect(workInProgress);
|
||||
}
|
||||
workInProgress.output = instance;
|
||||
} else {
|
||||
const instance = createInstance(workInProgress.type, newProps, children);
|
||||
// TODO: This seems like unnecessary duplication.
|
||||
workInProgress.stateNode = instance;
|
||||
workInProgress.output = instance;
|
||||
}
|
||||
return null;
|
||||
case CoroutineComponent:
|
||||
console.log('/coroutine component', workInProgress.pendingProps.handler.name);
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
transferOutput(workInProgress.stateNode, workInProgress);
|
||||
// Reset the tag to now be a first phase coroutine.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
return null;
|
||||
case YieldComponent:
|
||||
// Does nothing.
|
||||
return null;
|
||||
|
||||
// Error cases
|
||||
case IndeterminateComponent:
|
||||
throw new Error('An indeterminate component should have become determinate before completing.');
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
completeWork,
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberPendingWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
var ReactFiber = require('ReactFiber');
|
||||
|
||||
var {
|
||||
NoWork,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
function cloneSiblings(current : Fiber, workInProgress : Fiber, returnFiber : Fiber) {
|
||||
while (current.sibling) {
|
||||
current = current.sibling;
|
||||
workInProgress = workInProgress.sibling = ReactFiber.cloneFiber(
|
||||
current,
|
||||
current.pendingWorkPriority
|
||||
);
|
||||
workInProgress.return = returnFiber;
|
||||
}
|
||||
workInProgress.sibling = null;
|
||||
}
|
||||
|
||||
exports.findNextUnitOfWorkAtPriority = function(currentRoot : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
|
||||
let current = currentRoot;
|
||||
while (current) {
|
||||
if (current.pendingWorkPriority !== NoWork &&
|
||||
current.pendingWorkPriority <= priorityLevel) {
|
||||
// This node has work to do that fits our priority level criteria.
|
||||
if (current.pendingProps !== null) {
|
||||
// We found some work to do. We need to return the "work in progress"
|
||||
// of this node which will be the alternate.
|
||||
const workInProgress = current.alternate;
|
||||
if (!workInProgress) {
|
||||
throw new Error('Should have wip now');
|
||||
}
|
||||
workInProgress.pendingProps = current.pendingProps;
|
||||
return workInProgress;
|
||||
}
|
||||
// If we have a child let's see if any of our children has work to do.
|
||||
// Only bother doing this at all if the current priority level matches
|
||||
// because it is the highest priority for the whole subtree.
|
||||
// TODO: Coroutines can have work in their stateNode which is another
|
||||
// type of child that needs to be searched for work.
|
||||
if (current.child) {
|
||||
// Ensure we have a work in progress copy to backtrack through.
|
||||
let currentChild = current.child;
|
||||
let workInProgress = current.alternate;
|
||||
if (!workInProgress) {
|
||||
throw new Error('Should have wip now');
|
||||
}
|
||||
workInProgress.pendingWorkPriority = current.pendingWorkPriority;
|
||||
workInProgress.child = ReactFiber.cloneFiber(currentChild, NoWork);
|
||||
workInProgress.child.return = workInProgress;
|
||||
cloneSiblings(currentChild, workInProgress.child, workInProgress);
|
||||
current = current.child;
|
||||
continue;
|
||||
}
|
||||
// If we match the priority but has no child and no work to do,
|
||||
// then we can safely reset the flag.
|
||||
current.pendingWorkPriority = NoWork;
|
||||
}
|
||||
if (current === currentRoot) {
|
||||
return null;
|
||||
}
|
||||
while (!current.sibling) {
|
||||
current = current.return;
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
if (current.pendingWorkPriority <= priorityLevel) {
|
||||
// If this subtree had work left to do, we would have returned it by
|
||||
// now. This could happen if a child with pending work gets cleaned up
|
||||
// but we don't clear the flag then. It is safe to reset it now.
|
||||
current.pendingWorkPriority = NoWork;
|
||||
}
|
||||
}
|
||||
current = current.sibling;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -13,135 +13,98 @@
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
var ReactFiber = require('ReactFiber');
|
||||
var { beginWork } = require('ReactFiberBeginWork');
|
||||
var { completeWork } = require('ReactFiberCompleteWork');
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { TypeOfWork } from 'ReactTypeOfWork';
|
||||
|
||||
type ReactHostElement<T, P> = {
|
||||
type: T,
|
||||
props: P
|
||||
};
|
||||
var { createFiberRoot } = require('ReactFiberRoot');
|
||||
var ReactFiberScheduler = require('ReactFiberScheduler');
|
||||
|
||||
var {
|
||||
LowPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
type Deadline = {
|
||||
timeRemaining : () => number
|
||||
};
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
type HostChildNode<I> = { tag: TypeOfWork, output: HostChildren<I>, sibling: any };
|
||||
|
||||
export type HostConfig<T, P, I> = {
|
||||
export type HostChildren<I> = null | void | I | HostChildNode<I>;
|
||||
|
||||
export type HostConfig<T, P, I, C> = {
|
||||
|
||||
// TODO: We don't currently have a quick way to detect that children didn't
|
||||
// reorder so we host will always need to check the set. We should make a flag
|
||||
// or something so that it can bailout easily.
|
||||
|
||||
updateContainer(containerInfo : C, children : HostChildren<I>) : void;
|
||||
|
||||
createInstance(type : T, props : P, children : HostChildren<I>) : I,
|
||||
prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : bool,
|
||||
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : void,
|
||||
deleteInstance(instance : I) : void,
|
||||
|
||||
createHostInstance(element : ReactHostElement<T, P>) : I,
|
||||
scheduleHighPriCallback(callback : () => void) : void,
|
||||
scheduleLowPriCallback(callback : (deadline : Deadline) => void) : void
|
||||
|
||||
};
|
||||
|
||||
type OpaqueID = {};
|
||||
type OpaqueNode = Fiber;
|
||||
|
||||
export type Reconciler = {
|
||||
mountNewRoot(element : ReactElement<any>) : OpaqueID;
|
||||
export type Reconciler<C> = {
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
|
||||
unmountContainer(container : OpaqueNode) : void,
|
||||
|
||||
// Used to extract the return value from the initial render. Legacy API.
|
||||
getPublicRootInstance(container : OpaqueNode) : (C | null),
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconciler<C> {
|
||||
|
||||
// const scheduleHighPriCallback = config.scheduleHighPriCallback;
|
||||
const scheduleLowPriCallback = config.scheduleLowPriCallback;
|
||||
|
||||
let nextUnitOfWork : ?Fiber = null;
|
||||
|
||||
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
while (true) {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = completeWork(current, workInProgress);
|
||||
if (next) {
|
||||
// If completing this work spawned new work, do that next.
|
||||
return next;
|
||||
} else if (workInProgress.sibling) {
|
||||
// If there is more work to do in this parent, do that next.
|
||||
return workInProgress.sibling;
|
||||
} else if (workInProgress.parent) {
|
||||
// If there's no more work in this parent. Complete the parent.
|
||||
// TODO: Stop using the parent for this purpose. I think this will break
|
||||
// down in edge cases because when nodes are reused during bailouts, we
|
||||
// don't know which of two parents was used. Instead we should maintain
|
||||
// a temporary manual stack.
|
||||
// $FlowFixMe: This downcast is not safe. It is intentionally an error.
|
||||
workInProgress = workInProgress.parent;
|
||||
} else {
|
||||
// If we're at the root, there's no more work to do.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = beginWork(current, workInProgress);
|
||||
if (next) {
|
||||
// If this spawns new work, do that next.
|
||||
return next;
|
||||
} else {
|
||||
// Otherwise, complete the current work.
|
||||
return completeUnitOfWork(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
function performLowPriWork(deadline : Deadline) {
|
||||
while (nextUnitOfWork) {
|
||||
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
} else {
|
||||
scheduleLowPriCallback(performLowPriWork);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureLowPriIsScheduled() {
|
||||
if (nextUnitOfWork) {
|
||||
return;
|
||||
}
|
||||
scheduleLowPriCallback(performLowPriWork);
|
||||
}
|
||||
|
||||
/*
|
||||
function performHighPriWork() {
|
||||
// There is no such thing as high pri work yet.
|
||||
}
|
||||
|
||||
function ensureHighPriIsScheduled() {
|
||||
scheduleHighPriCallback(performHighPriWork);
|
||||
}
|
||||
*/
|
||||
|
||||
let rootFiber : ?Fiber = null;
|
||||
var { scheduleLowPriWork } = ReactFiberScheduler(config);
|
||||
|
||||
return {
|
||||
|
||||
mountNewRoot(element : ReactElement<any>) : OpaqueID {
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode {
|
||||
const root = createFiberRoot(containerInfo);
|
||||
const container = root.current;
|
||||
// TODO: Use pending work/state instead of props.
|
||||
container.pendingProps = element;
|
||||
container.pendingWorkPriority = LowPriority;
|
||||
|
||||
ensureLowPriIsScheduled();
|
||||
scheduleLowPriWork(root);
|
||||
|
||||
// TODO: Unify this with ReactChildFiber. We can't now because the parent
|
||||
// is passed. Should be doable though. Might require a wrapper don't know.
|
||||
if (rootFiber && rootFiber.type === element.type && rootFiber.key === element.key) {
|
||||
nextUnitOfWork = ReactFiber.cloneFiber(rootFiber);
|
||||
nextUnitOfWork.input = element.props;
|
||||
return {};
|
||||
}
|
||||
// It may seem strange that we don't return the root here, but that will
|
||||
// allow us to have containers that are in the middle of the tree instead
|
||||
// of being roots.
|
||||
return container;
|
||||
},
|
||||
|
||||
nextUnitOfWork = rootFiber = ReactFiber.createFiberFromElement(element);
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = element;
|
||||
root.current.pendingWorkPriority = LowPriority;
|
||||
|
||||
return {};
|
||||
scheduleLowPriWork(root);
|
||||
},
|
||||
|
||||
unmountContainer(container : OpaqueNode) : void {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = [];
|
||||
root.current.pendingWorkPriority = LowPriority;
|
||||
|
||||
scheduleLowPriWork(root);
|
||||
},
|
||||
|
||||
getPublicRootInstance(container : OpaqueNode) : (C | null) {
|
||||
return null;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberRoot
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
const { createHostContainerFiber } = require('ReactFiber');
|
||||
|
||||
export type FiberRoot = {
|
||||
// Any additional information from the host associated with this root.
|
||||
containerInfo: any,
|
||||
// The currently active root fiber. This is the mutable root of the tree.
|
||||
current: Fiber,
|
||||
// Determines if this root has already been added to the schedule for work.
|
||||
isScheduled: bool,
|
||||
// The work schedule is a linked list.
|
||||
nextScheduledRoot: ?FiberRoot,
|
||||
};
|
||||
|
||||
exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
|
||||
// Cyclic construction. This cheats the type system right now because
|
||||
// stateNode is any.
|
||||
const uninitializedFiber = createHostContainerFiber();
|
||||
const root = {
|
||||
current: uninitializedFiber,
|
||||
containerInfo: containerInfo,
|
||||
isScheduled: false,
|
||||
nextScheduledRoot: null,
|
||||
};
|
||||
uninitializedFiber.stateNode = root;
|
||||
return root;
|
||||
};
|
||||
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberScheduler
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactFiberBeginWork = require('ReactFiberBeginWork');
|
||||
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
|
||||
var ReactFiberCommitWork = require('ReactFiberCommitWork');
|
||||
|
||||
var { cloneFiber } = require('ReactFiber');
|
||||
var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork');
|
||||
|
||||
var {
|
||||
NoWork,
|
||||
HighPriority,
|
||||
LowPriority,
|
||||
OffscreenPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
const { beginWork } = ReactFiberBeginWork(config);
|
||||
const { completeWork } = ReactFiberCompleteWork(config);
|
||||
const { commitWork } = ReactFiberCommitWork(config);
|
||||
|
||||
// const scheduleHighPriCallback = config.scheduleHighPriCallback;
|
||||
const scheduleLowPriCallback = config.scheduleLowPriCallback;
|
||||
|
||||
// The next work in progress fiber that we're currently working on.
|
||||
let nextUnitOfWork : ?Fiber = null;
|
||||
|
||||
// Linked list of roots with scheduled work on them.
|
||||
let nextScheduledRoot : ?FiberRoot = null;
|
||||
let lastScheduledRoot : ?FiberRoot = null;
|
||||
|
||||
function findNextUnitOfWork() {
|
||||
// Clear out roots with no more work on them.
|
||||
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
|
||||
nextScheduledRoot.isScheduled = false;
|
||||
if (nextScheduledRoot === lastScheduledRoot) {
|
||||
nextScheduledRoot = null;
|
||||
lastScheduledRoot = null;
|
||||
return null;
|
||||
}
|
||||
nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
|
||||
}
|
||||
let root = nextScheduledRoot;
|
||||
while (root) {
|
||||
cloneFiber(root.current, root.current.pendingWorkPriority);
|
||||
// Find the highest possible priority work to do.
|
||||
// This loop is unrolled just to satisfy Flow's enum constraint.
|
||||
// We could make arbitrary many idle priority levels but having
|
||||
// too many just means flushing changes too often.
|
||||
let work = findNextUnitOfWorkAtPriority(root.current, HighPriority);
|
||||
if (work) {
|
||||
return work;
|
||||
}
|
||||
work = findNextUnitOfWorkAtPriority(root.current, LowPriority);
|
||||
if (work) {
|
||||
return work;
|
||||
}
|
||||
work = findNextUnitOfWorkAtPriority(root.current, OffscreenPriority);
|
||||
if (work) {
|
||||
return work;
|
||||
}
|
||||
// We didn't find anything to do in this root, so let's try the next one.
|
||||
root = root.nextScheduledRoot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function commitAllWork(finishedWork : Fiber) {
|
||||
// Commit all the side-effects within a tree.
|
||||
// TODO: Error handling.
|
||||
let effectfulFiber = finishedWork.firstEffect;
|
||||
while (effectfulFiber) {
|
||||
commitWork(effectfulFiber);
|
||||
const next = effectfulFiber.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.
|
||||
effectfulFiber.nextEffect = null;
|
||||
effectfulFiber = next;
|
||||
}
|
||||
}
|
||||
|
||||
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
while (true) {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = completeWork(current, workInProgress);
|
||||
|
||||
// The work is now done. We don't need this anymore. This flags
|
||||
// to the system not to redo any work here.
|
||||
workInProgress.pendingProps = null;
|
||||
if (workInProgress.pendingWorkPriority === NoWork) {
|
||||
workInProgress.hasWorkInProgress = false;
|
||||
}
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
|
||||
if (returnFiber) {
|
||||
// Ensure that remaining work priority bubbles up.
|
||||
if (workInProgress.pendingWorkPriority !== NoWork &&
|
||||
(returnFiber.pendingWorkPriority === NoWork ||
|
||||
returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
|
||||
returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority;
|
||||
}
|
||||
// Ensure that the first and last effect of the parent corresponds
|
||||
// to the children's first and last effect. This probably relies on
|
||||
// children completing in order.
|
||||
if (!returnFiber.firstEffect) {
|
||||
returnFiber.firstEffect = workInProgress.firstEffect;
|
||||
}
|
||||
if (workInProgress.lastEffect) {
|
||||
if (returnFiber.lastEffect) {
|
||||
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
|
||||
}
|
||||
returnFiber.lastEffect = workInProgress.lastEffect;
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
// If completing this work spawned new work, do that next.
|
||||
return next;
|
||||
} else if (workInProgress.sibling) {
|
||||
// If there is more work to do in this returnFiber, do that next.
|
||||
return workInProgress.sibling;
|
||||
} else if (returnFiber) {
|
||||
// If there's no more work in this returnFiber. Complete the returnFiber.
|
||||
workInProgress = returnFiber;
|
||||
} else {
|
||||
// If we're at the root, there's no more work to do. We can flush it.
|
||||
const root : FiberRoot = (workInProgress.stateNode : any);
|
||||
root.current = workInProgress;
|
||||
// TODO: We can be smarter here and only look for more work in the
|
||||
// "next" scheduled work since we've already scanned passed. That
|
||||
// also ensures that work scheduled during reconciliation gets deferred.
|
||||
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
|
||||
console.log('----- COMPLETED with remaining work:', workInProgress.pendingWorkPriority);
|
||||
commitAllWork(workInProgress);
|
||||
const nextWork = findNextUnitOfWork();
|
||||
// if (!nextWork && hasMoreWork) {
|
||||
// TODO: This can happen when some deep work completes and we don't
|
||||
// know if this was the last one. We should be able to keep track of
|
||||
// the highest priority still in the tree for one pass. But if we
|
||||
// terminate an update we don't know.
|
||||
// throw new Error('FiberRoots should not have flagged more work if there is none.');
|
||||
// }
|
||||
return nextWork;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
// Ignore work if there is nothing to do.
|
||||
if (workInProgress.pendingProps === null) {
|
||||
return completeUnitOfWork(workInProgress);
|
||||
}
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = beginWork(current, workInProgress);
|
||||
if (next) {
|
||||
// If this spawns new work, do that next.
|
||||
return next;
|
||||
} else {
|
||||
// Otherwise, complete the current work.
|
||||
return completeUnitOfWork(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
function performLowPriWork(deadline) {
|
||||
if (!nextUnitOfWork) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (nextUnitOfWork) {
|
||||
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
if (!nextUnitOfWork) {
|
||||
// Find more work. We might have time to complete some more.
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
} else {
|
||||
scheduleLowPriCallback(performLowPriWork);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleLowPriWork(root : FiberRoot) {
|
||||
// We must reset the current unit of work pointer so that we restart the
|
||||
// search from the root during the next tick, in case there is now higher
|
||||
// priority work somewhere earlier than before.
|
||||
nextUnitOfWork = null;
|
||||
|
||||
if (root.isScheduled) {
|
||||
// If we're already scheduled, we can bail out.
|
||||
return;
|
||||
}
|
||||
root.isScheduled = true;
|
||||
if (lastScheduledRoot) {
|
||||
// Schedule ourselves to the end.
|
||||
lastScheduledRoot.nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
} else {
|
||||
// We're the only work scheduled.
|
||||
nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
scheduleLowPriCallback(performLowPriWork);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function performHighPriWork() {
|
||||
// There is no such thing as high pri work yet.
|
||||
}
|
||||
|
||||
function ensureHighPriIsScheduled() {
|
||||
scheduleHighPriCallback(performHighPriWork);
|
||||
}
|
||||
*/
|
||||
|
||||
return {
|
||||
scheduleLowPriWork: scheduleLowPriWork,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactPriorityLevel
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
module.exports = {
|
||||
NoWork: 0, // No work is pending.
|
||||
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
|
||||
AnimationPriority: 2, // Needs to complete before the next frame.
|
||||
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
|
||||
LowPriority: 4, // Data fetching, or result from updating stores.
|
||||
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
|
||||
};
|
||||
+9
-8
@@ -6,20 +6,21 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactTypesOfWork
|
||||
* @providesModule ReactTypeOfWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var TypesOfWork = {
|
||||
export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
module.exports = {
|
||||
IndeterminateComponent: 0, // Before we know whether it is functional or class
|
||||
FunctionalComponent: 1,
|
||||
ClassComponent: 2,
|
||||
HostComponent: 3,
|
||||
CoroutineComponent: 4,
|
||||
CoroutineHandlerPhase: 5,
|
||||
YieldComponent: 6,
|
||||
HostContainer: 3, // Root of a host tree. Could be nested inside another node.
|
||||
HostComponent: 4,
|
||||
CoroutineComponent: 5,
|
||||
CoroutineHandlerPhase: 6,
|
||||
YieldComponent: 7,
|
||||
};
|
||||
|
||||
module.exports = TypesOfWork;
|
||||
@@ -109,10 +109,332 @@ describe('ReactIncremental', function() {
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
ReactNoop.flush();
|
||||
|
||||
// TODO: Test bail out of host components. This is currently unobservable.
|
||||
|
||||
// Since this is an update, it should bail out and reuse the work from
|
||||
// Header and Content.
|
||||
expect(ops).toEqual(['Foo', 'Content']);
|
||||
|
||||
});
|
||||
|
||||
it('can cancel partially rendered work and restart', function() {
|
||||
|
||||
var ops = [];
|
||||
|
||||
function Bar(props) {
|
||||
ops.push('Bar');
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div>
|
||||
<Bar>{props.text}</Bar>
|
||||
<Bar>{props.text}</Bar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" />);
|
||||
ReactNoop.flush();
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
// Flush part of the work
|
||||
ReactNoop.flushLowPri(20);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// This will abort the previous work and restart
|
||||
ReactNoop.render(<Foo text="baz" />);
|
||||
|
||||
// Flush part of the new work
|
||||
ReactNoop.flushLowPri(20);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar']);
|
||||
|
||||
// Flush the rest of the work which now includes the low priority
|
||||
ReactNoop.flush(20);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
|
||||
|
||||
});
|
||||
|
||||
it('can deprioritize unfinished work and resume it later', function() {
|
||||
|
||||
var ops = [];
|
||||
|
||||
function Bar(props) {
|
||||
ops.push('Bar');
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Middle(props) {
|
||||
ops.push('Middle');
|
||||
return <span>{props.children}</span>;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div>
|
||||
<Bar>{props.text}</Bar>
|
||||
<section hidden={true}>
|
||||
<Middle>{props.text}</Middle>
|
||||
</section>
|
||||
<Bar>{props.text}</Bar>
|
||||
<footer hidden={true}>
|
||||
<Middle>Footer</Middle>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Render part of the work. This should be enough to flush everything except
|
||||
// the middle which has lower priority.
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
ReactNoop.flushLowPri(40);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Flush only the remaining work
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Middle', 'Middle']);
|
||||
|
||||
});
|
||||
|
||||
it('can resume work in a subtree even when a parent bails out', function() {
|
||||
|
||||
var ops = [];
|
||||
|
||||
function Bar(props) {
|
||||
ops.push('Bar');
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Tester() {
|
||||
// This component is just here to ensure that the bail out is
|
||||
// in fact in effect in the expected place for this test.
|
||||
ops.push('Tester');
|
||||
return <div />;
|
||||
}
|
||||
|
||||
function Middle(props) {
|
||||
ops.push('Middle');
|
||||
return <span>{props.children}</span>;
|
||||
}
|
||||
|
||||
var middleContent = (
|
||||
<aaa>
|
||||
<Tester />
|
||||
<bbb hidden={true}>
|
||||
<ccc>
|
||||
<Middle>Hi</Middle>
|
||||
</ccc>
|
||||
</bbb>
|
||||
</aaa>
|
||||
);
|
||||
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div>
|
||||
<Bar>{props.text}</Bar>
|
||||
{middleContent}
|
||||
<Bar>{props.text}</Bar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" />);
|
||||
ReactNoop.flushLowPri(52);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Tester', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// We're now rendering an update that will bail out on updating middle.
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
ReactNoop.flushLowPri(45);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Flush the rest to make sure that the bailout didn't block this work.
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['Middle']);
|
||||
});
|
||||
|
||||
it('can resume work in a bailed subtree within one pass', function() {
|
||||
|
||||
var ops = [];
|
||||
|
||||
function Bar(props) {
|
||||
ops.push('Bar');
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Tester() {
|
||||
// This component is just here to ensure that the bail out is
|
||||
// in fact in effect in the expected place for this test.
|
||||
ops.push('Tester');
|
||||
return <div />;
|
||||
}
|
||||
|
||||
function Middle(props) {
|
||||
ops.push('Middle');
|
||||
return <span>{props.children}</span>;
|
||||
}
|
||||
|
||||
var middleContent = (
|
||||
<aaa>
|
||||
<Tester />
|
||||
<bbb hidden={true}>
|
||||
<ccc>
|
||||
<Middle>Hi</Middle>
|
||||
</ccc>
|
||||
</bbb>
|
||||
</aaa>
|
||||
);
|
||||
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div hidden={props.text === 'bar'}>
|
||||
<Bar>{props.text}</Bar>
|
||||
{middleContent}
|
||||
<Bar>{props.text}</Bar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" />);
|
||||
ReactNoop.flushLowPri(52);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Tester', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Make a quick update which will create a low pri tree on top of the
|
||||
// already low pri tree.
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
ReactNoop.flushLowPri(15);
|
||||
|
||||
expect(ops).toEqual(['Foo']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// At this point, middle will bail out but it has not yet fully rendered.
|
||||
// Since that is the same priority as its parent tree. This should render
|
||||
// as a single batch. Therefore, it is correct that Middle should be in the
|
||||
// middle. If it occurs after the two "Bar" components then it was flushed
|
||||
// after them which is not correct.
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['Bar', 'Middle', 'Bar']);
|
||||
|
||||
});
|
||||
|
||||
it('can reuse work done after being preempted', function() {
|
||||
|
||||
var ops = [];
|
||||
|
||||
function Bar(props) {
|
||||
ops.push('Bar');
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Middle(props) {
|
||||
ops.push('Middle');
|
||||
return <span>{props.children}</span>;
|
||||
}
|
||||
|
||||
var middleContent = (
|
||||
<div>
|
||||
<Middle>Hello</Middle>
|
||||
<Bar>-</Bar>
|
||||
<Middle>World</Middle>
|
||||
</div>
|
||||
);
|
||||
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div>
|
||||
<Bar>{props.text}</Bar>
|
||||
<div hidden={true}>
|
||||
{
|
||||
props.step === 0 ?
|
||||
<div>
|
||||
<Middle>Hi</Middle>
|
||||
<Bar>{props.text}</Bar>
|
||||
<Middle>There</Middle>
|
||||
</div>
|
||||
: middleContent
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Start rendering an update
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" step={0} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Middle', 'Bar', 'Middle']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Make a quick update which will schedule low priority work to
|
||||
// update the middle content.
|
||||
ReactNoop.render(<Foo text="bar" step={1} />);
|
||||
ReactNoop.flushLowPri(30);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// The middle content is now pending rendering...
|
||||
ReactNoop.flushLowPri(30);
|
||||
expect(ops).toEqual(['Middle', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// but we'll interupt it to render some higher priority work.
|
||||
// The middle content will bailout so it remains untouched.
|
||||
ReactNoop.render(<Foo text="foo" step={1} />);
|
||||
ReactNoop.flushLowPri(30);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Since we did nothing to the middle subtree during the interuption,
|
||||
// we should be able to reuse the reconciliation work that we already did
|
||||
// without restarting.
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['Middle']);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
|
||||
describe('ReactIncremental', function() {
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactNoop = require('ReactNoop');
|
||||
spyOn(console, 'log');
|
||||
});
|
||||
|
||||
function div(...children) {
|
||||
return { type: 'div', children };
|
||||
}
|
||||
|
||||
function span(...children) {
|
||||
return { type: 'span', children };
|
||||
}
|
||||
|
||||
it('can update child nodes of a host instance', function() {
|
||||
|
||||
function Bar(props) {
|
||||
return <span>{props.text}</span>;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return (
|
||||
<div>
|
||||
<Bar text={props.text} />
|
||||
{props.text === 'World' ? <Bar text={props.text} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo text="Hello" />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
div(span()),
|
||||
]);
|
||||
|
||||
ReactNoop.render(<Foo text="World" />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
div(span(), span()),
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('does not update child nodes if a flush is aborted', function() {
|
||||
|
||||
function Bar(props) {
|
||||
return <span>{props.text}</span>;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Bar text={props.text} />
|
||||
{props.text === 'Hello' ? <Bar text={props.text} /> : null}
|
||||
</div>
|
||||
<Bar text="Yo" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo text="Hello" />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
div(div(span(), span()), span()),
|
||||
]);
|
||||
|
||||
ReactNoop.render(<Foo text="World" />);
|
||||
ReactNoop.flushLowPri(35);
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
div(div(span(), span()), span()),
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
// TODO: Test that side-effects are not cut off when a work in progress node
|
||||
// moves to "current" without flushing due to having lower priority. Does this
|
||||
// even happen? Maybe a child doesn't get processed because it is lower prio?
|
||||
|
||||
});
|
||||
@@ -496,7 +496,7 @@ var ResponderEventPlugin = {
|
||||
);
|
||||
}
|
||||
|
||||
ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent, nativeEventTarget);
|
||||
ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
|
||||
|
||||
var extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent) ?
|
||||
setResponderAndExtractTransfer(
|
||||
|
||||
@@ -7,37 +7,44 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ResponderTouchHistoryStore
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
const EventPluginUtils = require('EventPluginUtils');
|
||||
|
||||
var invariant = require('invariant');
|
||||
const invariant = require('invariant');
|
||||
const warning = require('warning');
|
||||
|
||||
var isMoveish = EventPluginUtils.isMoveish;
|
||||
var isStartish = EventPluginUtils.isStartish;
|
||||
var isEndish = EventPluginUtils.isEndish;
|
||||
|
||||
var MAX_TOUCH_BANK = 20;
|
||||
const {
|
||||
isEndish,
|
||||
isMoveish,
|
||||
isStartish,
|
||||
} = EventPluginUtils;
|
||||
|
||||
/**
|
||||
* Touch position/time tracking information by touchID. Typically, we'll only
|
||||
* see IDs with a range of 1-20 (they are recycled when touches end and then
|
||||
* start again). This data is commonly needed by many different interaction
|
||||
* logic modules so precomputing it is very helpful to do once.
|
||||
* Each touch object in `touchBank` is of the following form:
|
||||
* { touchActive: boolean,
|
||||
* startTimeStamp: number,
|
||||
* startPageX: number,
|
||||
* startPageY: number,
|
||||
* currentPageX: number,
|
||||
* currentPageY: number,
|
||||
* currentTimeStamp: number
|
||||
* }
|
||||
* Tracks the position and time of each active touch by `touch.identifier`. We
|
||||
* should typically only see IDs in the range of 1-20 because IDs get recycled
|
||||
* when touches end and start again.
|
||||
*/
|
||||
var touchHistory = {
|
||||
touchBank: [],
|
||||
type TouchRecord = {
|
||||
touchActive: boolean,
|
||||
startPageX: number,
|
||||
startPageY: number,
|
||||
startTimeStamp: number,
|
||||
currentPageX: number,
|
||||
currentPageY: number,
|
||||
currentTimeStamp: number,
|
||||
previousPageX: number,
|
||||
previousPageY: number,
|
||||
previousTimeStamp: number,
|
||||
};
|
||||
|
||||
const MAX_TOUCH_BANK = 20;
|
||||
const touchBank: Array<TouchRecord> = [];
|
||||
const touchHistory = {
|
||||
touchBank,
|
||||
numberActiveTouches: 0,
|
||||
// If there is only one active touch, we remember its location. This prevents
|
||||
// us having to loop through all of the touches all the time in the most
|
||||
@@ -46,24 +53,34 @@ var touchHistory = {
|
||||
mostRecentTimeStamp: 0,
|
||||
};
|
||||
|
||||
var timestampForTouch = function(touch) {
|
||||
type Touch = {
|
||||
identifier: ?number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
timestamp: number,
|
||||
};
|
||||
type TouchEvent = {
|
||||
changedTouches: Array<Touch>,
|
||||
touches: Array<Touch>,
|
||||
};
|
||||
|
||||
function timestampForTouch(touch: Touch): number {
|
||||
// The legacy internal implementation provides "timeStamp", which has been
|
||||
// renamed to "timestamp". Let both work for now while we iron it out
|
||||
// TODO (evv): rename timeStamp to timestamp in internal code
|
||||
return touch.timeStamp || touch.timestamp;
|
||||
};
|
||||
return (touch: any).timeStamp || touch.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Instead of making gestures recompute filtered velocity, we could
|
||||
* include a built in velocity computation that can be reused globally.
|
||||
* @param {Touch} touch Native touch object.
|
||||
*/
|
||||
var initializeTouchData = function(touch) {
|
||||
function createTouchRecord(touch: Touch): TouchRecord {
|
||||
return {
|
||||
touchActive: true,
|
||||
startTimeStamp: timestampForTouch(touch),
|
||||
startPageX: touch.pageX,
|
||||
startPageY: touch.pageY,
|
||||
startTimeStamp: timestampForTouch(touch),
|
||||
currentPageX: touch.pageX,
|
||||
currentPageY: touch.pageY,
|
||||
currentTimeStamp: timestampForTouch(touch),
|
||||
@@ -71,114 +88,140 @@ var initializeTouchData = function(touch) {
|
||||
previousPageY: touch.pageY,
|
||||
previousTimeStamp: timestampForTouch(touch),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var reinitializeTouchTrack = function(touchTrack, touch) {
|
||||
touchTrack.touchActive = true;
|
||||
touchTrack.startTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.startPageX = touch.pageX;
|
||||
touchTrack.startPageY = touch.pageY;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.previousPageX = touch.pageX;
|
||||
touchTrack.previousPageY = touch.pageY;
|
||||
touchTrack.previousTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
|
||||
touchRecord.touchActive = true;
|
||||
touchRecord.startPageX = touch.pageX;
|
||||
touchRecord.startPageY = touch.pageY;
|
||||
touchRecord.startTimeStamp = timestampForTouch(touch);
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchRecord.previousPageX = touch.pageX;
|
||||
touchRecord.previousPageY = touch.pageY;
|
||||
touchRecord.previousTimeStamp = timestampForTouch(touch);
|
||||
}
|
||||
|
||||
var validateTouch = function(touch) {
|
||||
var identifier = touch.identifier;
|
||||
invariant(identifier != null, 'Touch object is missing identifier');
|
||||
if (identifier > MAX_TOUCH_BANK) {
|
||||
console.warn(
|
||||
'Touch identifier ' + identifier + ' is greater than maximum ' +
|
||||
'supported ' + MAX_TOUCH_BANK + ' which causes performance issues ' +
|
||||
'backfilling array locations for all of the indices.'
|
||||
function getTouchIdentifier({identifier}: Touch): number {
|
||||
invariant(identifier != null, 'Touch object is missing identifier.');
|
||||
warning(
|
||||
identifier <= MAX_TOUCH_BANK,
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
identifier,
|
||||
MAX_TOUCH_BANK
|
||||
);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function recordTouchStart(touch: Touch): void {
|
||||
const identifier = getTouchIdentifier(touch);
|
||||
const touchRecord = touchBank[identifier];
|
||||
if (touchRecord) {
|
||||
resetTouchRecord(touchRecord, touch);
|
||||
} else {
|
||||
touchBank[identifier] = createTouchRecord(touch);
|
||||
}
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
}
|
||||
|
||||
function recordTouchMove(touch: Touch): void {
|
||||
const touchRecord = touchBank[getTouchIdentifier(touch)];
|
||||
if (touchRecord) {
|
||||
touchRecord.touchActive = true;
|
||||
touchRecord.previousPageX = touchRecord.currentPageX;
|
||||
touchRecord.previousPageY = touchRecord.currentPageY;
|
||||
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.error(
|
||||
'Cannot record touch move without a touch start.\n' +
|
||||
'Touch Move: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var recordStartTouchData = function(touch) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var identifier = touch.identifier;
|
||||
var touchTrack = touchBank[identifier];
|
||||
if (__DEV__) {
|
||||
validateTouch(touch);
|
||||
}
|
||||
if (touchTrack) {
|
||||
reinitializeTouchTrack(touchTrack, touch);
|
||||
function recordTouchEnd(touch: Touch): void {
|
||||
const touchRecord = touchBank[getTouchIdentifier(touch)];
|
||||
if (touchRecord) {
|
||||
touchRecord.touchActive = false;
|
||||
touchRecord.previousPageX = touchRecord.currentPageX;
|
||||
touchRecord.previousPageY = touchRecord.currentPageY;
|
||||
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
touchBank[touch.identifier] = initializeTouchData(touch);
|
||||
console.error(
|
||||
'Cannot record touch end without a touch start.\n' +
|
||||
'Touch End: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank()
|
||||
);
|
||||
}
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
}
|
||||
|
||||
var recordMoveTouchData = function(touch) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var touchTrack = touchBank[touch.identifier];
|
||||
if (__DEV__) {
|
||||
validateTouch(touch);
|
||||
invariant(touchTrack, 'Touch data should have been recorded on start');
|
||||
function printTouch(touch: Touch): string {
|
||||
return JSON.stringify({
|
||||
identifier: touch.identifier,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
timestamp: timestampForTouch(touch),
|
||||
});
|
||||
}
|
||||
|
||||
function printTouchBank(): string {
|
||||
let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK));
|
||||
if (touchBank.length > MAX_TOUCH_BANK) {
|
||||
printed += ' (original size: ' + touchBank.length + ')';
|
||||
}
|
||||
touchTrack.touchActive = true;
|
||||
touchTrack.previousPageX = touchTrack.currentPageX;
|
||||
touchTrack.previousPageY = touchTrack.currentPageY;
|
||||
touchTrack.previousTimeStamp = touchTrack.currentTimeStamp;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
return printed;
|
||||
}
|
||||
|
||||
var recordEndTouchData = function(touch) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var touchTrack = touchBank[touch.identifier];
|
||||
if (__DEV__) {
|
||||
validateTouch(touch);
|
||||
invariant(touchTrack, 'Touch data should have been recorded on start');
|
||||
}
|
||||
touchTrack.previousPageX = touchTrack.currentPageX;
|
||||
touchTrack.previousPageY = touchTrack.currentPageY;
|
||||
touchTrack.previousTimeStamp = touchTrack.currentTimeStamp;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.touchActive = false;
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
|
||||
var ResponderTouchHistoryStore = {
|
||||
recordTouchTrack: function(topLevelType, nativeEvent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
const ResponderTouchHistoryStore = {
|
||||
recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void {
|
||||
if (isMoveish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordMoveTouchData);
|
||||
nativeEvent.changedTouches.forEach(recordTouchMove);
|
||||
} else if (isStartish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordStartTouchData);
|
||||
nativeEvent.changedTouches.forEach(recordTouchStart);
|
||||
touchHistory.numberActiveTouches = nativeEvent.touches.length;
|
||||
if (touchHistory.numberActiveTouches === 1) {
|
||||
touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier;
|
||||
touchHistory.indexOfSingleActiveTouch =
|
||||
nativeEvent.touches[0].identifier;
|
||||
}
|
||||
} else if (isEndish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordEndTouchData);
|
||||
nativeEvent.changedTouches.forEach(recordTouchEnd);
|
||||
touchHistory.numberActiveTouches = nativeEvent.touches.length;
|
||||
if (touchHistory.numberActiveTouches === 1) {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrackToCheck = touchBank[i];
|
||||
for (let i = 0; i < touchBank.length; i++) {
|
||||
const touchTrackToCheck = touchBank[i];
|
||||
if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
|
||||
touchHistory.indexOfSingleActiveTouch = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
var activeTouchData = touchBank[touchHistory.indexOfSingleActiveTouch];
|
||||
var foundActive = activeTouchData != null && !!activeTouchData.touchActive;
|
||||
invariant(foundActive, 'Cannot find single active touch');
|
||||
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
|
||||
warning(
|
||||
activeRecord != null &&
|
||||
activeRecord.touchActive,
|
||||
'Cannot find single active touch.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
touchHistory: touchHistory,
|
||||
touchHistory,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var KeyEscapeUtils = require('KeyEscapeUtils');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
@@ -24,6 +23,7 @@ function instantiateChild(childInstances, child, name, selfDebugID) {
|
||||
// We found a component instance.
|
||||
var keyUnique = (childInstances[name] === undefined);
|
||||
if (__DEV__) {
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
warning(
|
||||
keyUnique,
|
||||
'flattenChildren(...): Encountered two children with the same key, ' +
|
||||
@@ -34,7 +34,7 @@ function instantiateChild(childInstances, child, name, selfDebugID) {
|
||||
);
|
||||
}
|
||||
if (child != null && keyUnique) {
|
||||
childInstances[name] = instantiateReactComponent(child);
|
||||
childInstances[name] = instantiateReactComponent(child, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ var ReactChildReconciler = {
|
||||
ReactReconciler.unmountComponent(prevChild, false);
|
||||
}
|
||||
// The child must be instantiated before it's mounted.
|
||||
var nextChildInstance = instantiateReactComponent(nextElement);
|
||||
var nextChildInstance = instantiateReactComponent(nextElement, true);
|
||||
nextChildren[name] = nextChildInstance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactNodeTypes = require('ReactNodeTypes');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
|
||||
@@ -197,8 +196,10 @@ var ReactCompositeComponentMixin = {
|
||||
|
||||
var Component = this._currentElement.type;
|
||||
|
||||
var updateQueue = transaction.getUpdateQueue();
|
||||
|
||||
// Initialize the public class
|
||||
var inst = this._constructComponent(publicProps, publicContext);
|
||||
var inst = this._constructComponent(publicProps, publicContext, updateQueue);
|
||||
var renderedElement;
|
||||
|
||||
// Support functional components
|
||||
@@ -245,7 +246,7 @@ var ReactCompositeComponentMixin = {
|
||||
inst.props = publicProps;
|
||||
inst.context = publicContext;
|
||||
inst.refs = emptyObject;
|
||||
inst.updater = ReactUpdateQueue;
|
||||
inst.updater = updateQueue;
|
||||
|
||||
this._instance = inst;
|
||||
|
||||
@@ -345,20 +346,20 @@ var ReactCompositeComponentMixin = {
|
||||
return markup;
|
||||
},
|
||||
|
||||
_constructComponent: function(publicProps, publicContext) {
|
||||
_constructComponent: function(publicProps, publicContext, updateQueue) {
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = this;
|
||||
try {
|
||||
return this._constructComponentWithoutOwner(publicProps, publicContext);
|
||||
return this._constructComponentWithoutOwner(publicProps, publicContext, updateQueue);
|
||||
} finally {
|
||||
ReactCurrentOwner.current = null;
|
||||
}
|
||||
} else {
|
||||
return this._constructComponentWithoutOwner(publicProps, publicContext);
|
||||
return this._constructComponentWithoutOwner(publicProps, publicContext, updateQueue);
|
||||
}
|
||||
},
|
||||
|
||||
_constructComponentWithoutOwner: function(publicProps, publicContext) {
|
||||
_constructComponentWithoutOwner: function(publicProps, publicContext, updateQueue) {
|
||||
var Component = this._currentElement.type;
|
||||
var instanceOrElement;
|
||||
if (shouldConstruct(Component)) {
|
||||
@@ -370,7 +371,7 @@ var ReactCompositeComponentMixin = {
|
||||
);
|
||||
}
|
||||
}
|
||||
instanceOrElement = new Component(publicProps, publicContext, ReactUpdateQueue);
|
||||
instanceOrElement = new Component(publicProps, publicContext, updateQueue);
|
||||
if (__DEV__) {
|
||||
if (this._debugID !== 0) {
|
||||
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
||||
@@ -390,7 +391,7 @@ var ReactCompositeComponentMixin = {
|
||||
);
|
||||
}
|
||||
}
|
||||
instanceOrElement = Component(publicProps, publicContext, ReactUpdateQueue);
|
||||
instanceOrElement = Component(publicProps, publicContext, updateQueue);
|
||||
if (__DEV__) {
|
||||
if (this._debugID !== 0) {
|
||||
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
||||
@@ -415,6 +416,11 @@ var ReactCompositeComponentMixin = {
|
||||
try {
|
||||
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
||||
} catch (e) {
|
||||
if (__DEV__) {
|
||||
if (this._debugID !== 0) {
|
||||
ReactInstrumentation.debugTool.onError();
|
||||
}
|
||||
}
|
||||
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
|
||||
transaction.rollback(checkpoint);
|
||||
this._instance.unstable_handleError(e);
|
||||
@@ -465,9 +471,11 @@ var ReactCompositeComponentMixin = {
|
||||
renderedElement = this._renderValidatedComponent();
|
||||
}
|
||||
|
||||
this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
|
||||
var nodeType = ReactNodeTypes.getType(renderedElement);
|
||||
this._renderedNodeType = nodeType;
|
||||
var child = this._instantiateReactComponent(
|
||||
renderedElement
|
||||
renderedElement,
|
||||
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
|
||||
);
|
||||
this._renderedComponent = child;
|
||||
if (__DEV__) {
|
||||
@@ -975,9 +983,11 @@ var ReactCompositeComponentMixin = {
|
||||
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
|
||||
ReactReconciler.unmountComponent(prevComponentInstance, false);
|
||||
|
||||
this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
|
||||
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
|
||||
this._renderedNodeType = nodeType;
|
||||
var child = this._instantiateReactComponent(
|
||||
nextRenderedElement
|
||||
nextRenderedElement,
|
||||
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
|
||||
);
|
||||
this._renderedComponent = child;
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -162,10 +162,15 @@ if (__DEV__) {
|
||||
}
|
||||
};
|
||||
setChildrenForInstrumentation = function(children) {
|
||||
ReactInstrumentation.debugTool.onSetChildren(
|
||||
getDebugID(this),
|
||||
children ? Object.keys(children).map(key => children[key]._debugID) : []
|
||||
);
|
||||
var debugID = getDebugID(this);
|
||||
// TODO: React Native empty components are also multichild.
|
||||
// This means they still get into this method but don't have _debugID.
|
||||
if (debugID !== 0) {
|
||||
ReactInstrumentation.debugTool.onSetChildren(
|
||||
debugID,
|
||||
children ? Object.keys(children).map(key => children[key]._debugID) : []
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -7,10 +7,13 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNodeTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
type ReactNodeType = 0 | 1 | 2;
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
|
||||
var invariant = require('invariant');
|
||||
@@ -20,7 +23,7 @@ var ReactNodeTypes = {
|
||||
COMPOSITE: 1,
|
||||
EMPTY: 2,
|
||||
|
||||
getType: function(node) {
|
||||
getType: function(node: ReactElement<any>): ReactNodeType {
|
||||
if (node === null || node === false) {
|
||||
return ReactNodeTypes.EMPTY;
|
||||
} else if (ReactElement.isValidElement(node)) {
|
||||
@@ -246,8 +246,10 @@ var ReactUpdateQueue = {
|
||||
enqueueUpdate(internalInstance);
|
||||
},
|
||||
|
||||
enqueueElementInternal: function(internalInstance, newElement) {
|
||||
internalInstance._pendingElement = newElement;
|
||||
enqueueElementInternal: function(internalInstance, nextElement, nextContext) {
|
||||
internalInstance._pendingElement = nextElement;
|
||||
// TODO: introduce _pendingContext instead of setting it directly.
|
||||
internalInstance._context = nextContext;
|
||||
enqueueUpdate(internalInstance);
|
||||
},
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
var Transaction = require('Transaction');
|
||||
|
||||
@@ -193,10 +192,6 @@ function runBatchedUpdates(transaction) {
|
||||
}
|
||||
|
||||
var flushBatchedUpdates = function() {
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
|
||||
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
|
||||
// array and perform any updates enqueued by mount-ready handlers (i.e.,
|
||||
// componentDidUpdate) but we need to check here too in order to catch
|
||||
@@ -216,10 +211,6 @@ var flushBatchedUpdates = function() {
|
||||
CallbackQueue.release(queue);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -232,7 +223,7 @@ function enqueueUpdate(component) {
|
||||
// Various parts of our code (such as ReactCompositeComponent's
|
||||
// _renderValidatedComponent) assume that calls to render aren't nested;
|
||||
// verify that that's the case. (This is called by each top-level update
|
||||
// function, like setProps, setState, forceUpdate, etc.; creation and
|
||||
// function, like setState, forceUpdate, etc.; creation and
|
||||
// destruction of top-level components is guarded in ReactMount.)
|
||||
|
||||
if (!batchingStrategy.isBatchingUpdates) {
|
||||
|
||||
File renamed without changes.
@@ -239,6 +239,12 @@ function testPropsSequence(sequence) {
|
||||
describe('ReactMultiChildReconcile', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
ReactInstanceMap = require('ReactInstanceMap');
|
||||
mapObject = require('mapObject');
|
||||
});
|
||||
|
||||
it('should reset internal state if removed then readded', function() {
|
||||
|
||||
@@ -116,6 +116,10 @@ var expectClickLogsLengthToBe = function(instance, length) {
|
||||
describe('reactiverefs', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -159,6 +163,10 @@ describe('reactiverefs', function() {
|
||||
describe('ref swapping', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
});
|
||||
|
||||
var RefHopsAround = React.createClass({
|
||||
@@ -241,4 +249,3 @@ describe('ref swapping', function() {
|
||||
expect(!!instance.refs).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
File renamed without changes.
@@ -78,14 +78,14 @@ var nextDebugID = 1;
|
||||
* Given a ReactNode, create an instance that will actually be mounted.
|
||||
*
|
||||
* @param {ReactNode} node
|
||||
* @param {boolean} shouldHaveDebugID
|
||||
* @return {object} A new instance of the element's constructor.
|
||||
* @protected
|
||||
*/
|
||||
function instantiateReactComponent(node) {
|
||||
function instantiateReactComponent(node, shouldHaveDebugID) {
|
||||
var instance;
|
||||
|
||||
var isEmpty = node === null || node === false;
|
||||
if (isEmpty) {
|
||||
if (node === null || node === false) {
|
||||
instance = ReactEmptyComponent.create(instantiateReactComponent);
|
||||
} else if (typeof node === 'object') {
|
||||
var element = node;
|
||||
@@ -141,16 +141,17 @@ function instantiateReactComponent(node) {
|
||||
instance._mountImage = null;
|
||||
|
||||
if (__DEV__) {
|
||||
var debugID = isEmpty ? 0 : nextDebugID++;
|
||||
instance._debugID = debugID;
|
||||
|
||||
if (debugID !== 0) {
|
||||
if (shouldHaveDebugID) {
|
||||
var debugID = nextDebugID++;
|
||||
instance._debugID = debugID;
|
||||
var displayName = getDisplayName(instance);
|
||||
ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName);
|
||||
var owner = node && node._owner;
|
||||
if (owner) {
|
||||
ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID);
|
||||
}
|
||||
} else {
|
||||
instance._debugID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+1
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFeatureFlags
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule accumulate
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -20,28 +21,27 @@ var invariant = require('invariant');
|
||||
*
|
||||
* @return {*|array<*>} An accumulation of items.
|
||||
*/
|
||||
function accumulate(current, next) {
|
||||
function accumulate<T>(current: ?(T | Array<T>), next: T | Array<T>): T | Array<T> {
|
||||
invariant(
|
||||
next != null,
|
||||
'accumulate(...): Accumulated items must be not be null or undefined.'
|
||||
);
|
||||
|
||||
if (current == null) {
|
||||
return next;
|
||||
} else {
|
||||
// Both are not empty. Warning: Never call x.concat(y) when you are not
|
||||
// certain that x is an Array (x could be a string with concat method).
|
||||
var currentIsArray = Array.isArray(current);
|
||||
var nextIsArray = Array.isArray(next);
|
||||
if (currentIsArray) {
|
||||
return current.concat(next);
|
||||
} else {
|
||||
if (nextIsArray) {
|
||||
return [current].concat(next);
|
||||
} else {
|
||||
return [current, next];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Both are not empty. Warning: Never call x.concat(y) when you are not
|
||||
// certain that x is an Array (x could be a string with concat method).
|
||||
if (Array.isArray(current)) {
|
||||
return current.concat(next);
|
||||
}
|
||||
|
||||
if (Array.isArray(next)) {
|
||||
return [current].concat(next);
|
||||
}
|
||||
|
||||
return [current, next];
|
||||
}
|
||||
|
||||
module.exports = accumulate;
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule accumulateInto
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -14,7 +15,6 @@
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
*
|
||||
* Accumulates items that must not be null or undefined into the first one. This
|
||||
* is used to conserve memory by avoiding array allocations, and thus sacrifices
|
||||
* API cleanness. Since `current` can be null before being passed in and not
|
||||
@@ -27,31 +27,28 @@ var invariant = require('invariant');
|
||||
* @return {*|array<*>} An accumulation of items.
|
||||
*/
|
||||
|
||||
function accumulateInto(current, next) {
|
||||
function accumulateInto<T>(current: ?(T | Array<T>), next: T | Array<T>): T | Array<T> {
|
||||
invariant(
|
||||
next != null,
|
||||
'accumulateInto(...): Accumulated items must not be null or undefined.'
|
||||
);
|
||||
|
||||
if (current == null) {
|
||||
return next;
|
||||
}
|
||||
|
||||
// Both are not empty. Warning: Never call x.concat(y) when you are not
|
||||
// certain that x is an Array (x could be a string with concat method).
|
||||
var currentIsArray = Array.isArray(current);
|
||||
var nextIsArray = Array.isArray(next);
|
||||
|
||||
if (currentIsArray && nextIsArray) {
|
||||
current.push.apply(current, next);
|
||||
return current;
|
||||
}
|
||||
|
||||
if (currentIsArray) {
|
||||
if (Array.isArray(current)) {
|
||||
if (Array.isArray(next)) {
|
||||
current.push.apply(current, next);
|
||||
return current;
|
||||
}
|
||||
current.push(next);
|
||||
return current;
|
||||
}
|
||||
|
||||
if (nextIsArray) {
|
||||
if (Array.isArray(next)) {
|
||||
// A bit too dangerous to mutate `next`.
|
||||
return [current].concat(next);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule adler32
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -18,7 +19,7 @@ var MOD = 65521;
|
||||
// This implementation (a modified version of the SheetJS version) has been optimized
|
||||
// for our use case, at the expense of conforming to the adler32 specification
|
||||
// for non-ascii inputs.
|
||||
function adler32(data) {
|
||||
function adler32(data: string): number {
|
||||
var a = 1;
|
||||
var b = 0;
|
||||
var i = 0;
|
||||
+7
-2
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule forEachAccumulated
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -18,12 +19,16 @@
|
||||
* handling the case when there is exactly one item (and we do not need to
|
||||
* allocate an array).
|
||||
*/
|
||||
var forEachAccumulated = function(arr, cb, scope) {
|
||||
function forEachAccumulated<T>(
|
||||
arr: ?(T | Array<T>),
|
||||
cb: ((elem: T) => void),
|
||||
scope: ?any,
|
||||
) {
|
||||
if (Array.isArray(arr)) {
|
||||
arr.forEach(cb, scope);
|
||||
} else if (arr) {
|
||||
cb.call(scope, arr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = forEachAccumulated;
|
||||
+13
-6
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule isTextInputElement
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -14,7 +15,7 @@
|
||||
/**
|
||||
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
|
||||
*/
|
||||
var supportedInputTypes = {
|
||||
var supportedInputTypes: {[key: string]: true | void} = {
|
||||
'color': true,
|
||||
'date': true,
|
||||
'datetime': true,
|
||||
@@ -32,12 +33,18 @@ var supportedInputTypes = {
|
||||
'week': true,
|
||||
};
|
||||
|
||||
function isTextInputElement(elem) {
|
||||
function isTextInputElement(elem: ?HTMLElement): bool {
|
||||
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
|
||||
return nodeName && (
|
||||
(nodeName === 'input' && supportedInputTypes[elem.type]) ||
|
||||
nodeName === 'textarea'
|
||||
);
|
||||
|
||||
if (nodeName === 'input') {
|
||||
return !!supportedInputTypes[((elem: any): HTMLInputElement).type];
|
||||
}
|
||||
|
||||
if (nodeName === 'textarea') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = isTextInputElement;
|
||||
@@ -120,17 +120,7 @@ var ReactHostMount = {
|
||||
// }
|
||||
// }
|
||||
|
||||
var instance = instantiateReactComponent(nextWrappedElement);
|
||||
|
||||
if (__DEV__) {
|
||||
// Mute future events from the top level wrapper.
|
||||
// It is an implementation detail that devtools should not know about.
|
||||
instance._debugID = 0;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
}
|
||||
var instance = instantiateReactComponent(nextWrappedElement, false);
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
@@ -145,7 +135,6 @@ var ReactHostMount = {
|
||||
ReactInstrumentation.debugTool.onMountRootComponent(
|
||||
instance._renderedComponent._debugID
|
||||
);
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
return new ReactTestInstance(instance);
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var Transaction = require('Transaction');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
/**
|
||||
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
|
||||
@@ -81,6 +82,13 @@ var Mixin = {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return ReactUpdateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for this, and will invoke this before allowing this
|
||||
* instance to be reused.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule KeyEscapeUtils
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -14,10 +15,10 @@
|
||||
/**
|
||||
* Escape and wrap key so it is safe to use as a reactid
|
||||
*
|
||||
* @param {*} key to be escaped.
|
||||
* @param {string} key to be escaped.
|
||||
* @return {string} the escaped key.
|
||||
*/
|
||||
function escape(key) {
|
||||
function escape(key: string): string {
|
||||
var escapeRegex = /[=:]/g;
|
||||
var escaperLookup = {
|
||||
'=': '=0',
|
||||
@@ -39,7 +40,7 @@ function escape(key) {
|
||||
* @param {string} key to unescape.
|
||||
* @return {string} the unescaped key.
|
||||
*/
|
||||
function unescape(key) {
|
||||
function unescape(key: string): string {
|
||||
var unescapeRegex = /(=0|=2)/g;
|
||||
var unescaperLookup = {
|
||||
'=0': '=',
|
||||
|
||||
@@ -94,7 +94,7 @@ var DEFAULT_POOLER = oneArgumentPooler;
|
||||
* Augments `CopyConstructor` to be a poolable class, augmenting only the class
|
||||
* itself (statically) not adding any prototypical fields. Any CopyConstructor
|
||||
* you give this may have a `poolSize` property, and will look for a
|
||||
* prototypical `destructor` on instances (optional).
|
||||
* prototypical `destructor` on instances.
|
||||
*
|
||||
* @param {Function} CopyConstructor Constructor that can be used to reset.
|
||||
* @param {Function} pooler Customizable pooler.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule deprecated
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -24,7 +25,13 @@ var warning = require('warning');
|
||||
* @param {function} fn The function to forward on to
|
||||
* @return {function} The function that will warn once and then call fn
|
||||
*/
|
||||
function deprecated(fnName, newModule, newPackage, ctx, fn) {
|
||||
function deprecated<T: Function>(
|
||||
fnName: string,
|
||||
newModule: string,
|
||||
newPackage: string,
|
||||
ctx: mixed,
|
||||
fn: T,
|
||||
): T {
|
||||
var warned = false;
|
||||
if (__DEV__) {
|
||||
var newFn = function() {
|
||||
@@ -47,7 +54,12 @@ function deprecated(fnName, newModule, newPackage, ctx, fn) {
|
||||
};
|
||||
// We need to make sure all properties of the original fn are copied over.
|
||||
// In particular, this is needed to support PropTypes
|
||||
return Object.assign(newFn, fn);
|
||||
Object.assign(newFn, (fn: Object));
|
||||
|
||||
// Flow is not smart enough to figure out that newFn is of the same type as
|
||||
// fn. Since we don't want to lose out the type of the function, casting
|
||||
// to any and force flow to use T.
|
||||
return ((newFn: any): T);
|
||||
}
|
||||
|
||||
return fn;
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule flattenChildren
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
var KeyEscapeUtils = require('KeyEscapeUtils');
|
||||
var traverseAllChildren = require('traverseAllChildren');
|
||||
var warning = require('warning');
|
||||
@@ -22,22 +22,30 @@ var warning = require('warning');
|
||||
* @param {!string} name String name of key path to child.
|
||||
* @param {number=} selfDebugID Optional debugID of the current internal instance.
|
||||
*/
|
||||
function flattenSingleChildIntoContext(traverseContext, child, name, selfDebugID) {
|
||||
function flattenSingleChildIntoContext(
|
||||
traverseContext: mixed,
|
||||
child: ReactElement<any>,
|
||||
name: string,
|
||||
selfDebugID?: number,
|
||||
): void {
|
||||
// We found a component instance.
|
||||
var result = traverseContext;
|
||||
var keyUnique = (result[name] === undefined);
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
keyUnique,
|
||||
'flattenChildren(...): Encountered two children with the same key, ' +
|
||||
'`%s`. Child keys must be unique; when two children share a key, only ' +
|
||||
'the first child will be used.%s',
|
||||
KeyEscapeUtils.unescape(name),
|
||||
ReactComponentTreeDevtool.getStackAddendumByID(selfDebugID)
|
||||
);
|
||||
}
|
||||
if (keyUnique && child != null) {
|
||||
result[name] = child;
|
||||
if (traverseContext && typeof traverseContext === 'object') {
|
||||
const result = traverseContext;
|
||||
const keyUnique = (result[name] === undefined);
|
||||
if (__DEV__) {
|
||||
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
|
||||
warning(
|
||||
keyUnique,
|
||||
'flattenChildren(...): Encountered two children with the same key, ' +
|
||||
'`%s`. Child keys must be unique; when two children share a key, only ' +
|
||||
'the first child will be used.%s',
|
||||
KeyEscapeUtils.unescape(name),
|
||||
ReactComponentTreeDevtool.getStackAddendumByID(selfDebugID)
|
||||
);
|
||||
}
|
||||
if (keyUnique && child != null) {
|
||||
result[name] = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +54,10 @@ function flattenSingleChildIntoContext(traverseContext, child, name, selfDebugID
|
||||
* children will not be included in the resulting object.
|
||||
* @return {!object} flattened children keyed by name.
|
||||
*/
|
||||
function flattenChildren(children, selfDebugID) {
|
||||
function flattenChildren(
|
||||
children: ReactElement<any>,
|
||||
selfDebugID?: number,
|
||||
): ?{ [name: string]: ReactElement<any> } {
|
||||
if (children == null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule getIteratorFn
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -29,7 +30,7 @@ var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
|
||||
* @param {?object} maybeIterable
|
||||
* @return {?function}
|
||||
*/
|
||||
function getIteratorFn(maybeIterable) {
|
||||
function getIteratorFn(maybeIterable: ?any): ?(p: ReactElement<any>) => void {
|
||||
var iteratorFn = maybeIterable && (
|
||||
(ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL]) ||
|
||||
maybeIterable[FAUX_ITERATOR_SYMBOL]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule reactProdInvariant
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -16,7 +17,7 @@
|
||||
* and will _only_ be required by the corresponding babel pass.
|
||||
* It always throws.
|
||||
*/
|
||||
function reactProdInvariant(code) {
|
||||
function reactProdInvariant(code: string): void {
|
||||
var argCount = arguments.length - 1;
|
||||
|
||||
var message = (
|
||||
@@ -33,7 +34,7 @@ function reactProdInvariant(code) {
|
||||
' for full errors and additional helpful warnings.'
|
||||
);
|
||||
|
||||
var error = new Error(message);
|
||||
var error: Error & { framesToPop?: number } = new Error(message);
|
||||
error.name = 'Invariant Violation';
|
||||
error.framesToPop = 1; // we don't care about reactProdInvariant's own frame
|
||||
|
||||
|
||||
Reference in new issue
Block a user