Fix React constant elements transform from hoisting elements to positions where their referenced bindings haven't been evaluated yet (#3596)

This commit is contained in:
Sebastian McKenzie 2016-07-27 15:54:21 +01:00 committed by Henry Zhu
parent 8d14f9f4d0
commit 3b4b3656a8
9 changed files with 161 additions and 2 deletions

View File

@ -0,0 +1,14 @@
const AppItem = () => {
return <div>child</div>;
};
export default class App extends React.Component {
render() {
return (
<div>
<p>Parent</p>
<AppItem />
</div>
);
}
}

View File

@ -0,0 +1,16 @@
var _ref = <div>child</div>;
const AppItem = () => {
return _ref;
};
var _ref2 = <div>
<p>Parent</p>
<AppItem />
</div>;
export default class App extends React.Component {
render() {
return _ref2;
}
}

View File

@ -0,0 +1,16 @@
(function () {
class App extends React.Component {
render() {
return (
<div>
<p>Parent</p>
<AppItem />
</div>
);
}
}
const AppItem = () => {
return <div>child</div>;
};
});

View File

@ -0,0 +1,18 @@
var _ref = <p>Parent</p>;
var _ref2 = <div>child</div>;
(function () {
class App extends React.Component {
render() {
return <div>
{_ref}
<AppItem />
</div>;
}
}
const AppItem = () => {
return _ref2;
};
});

View File

@ -0,0 +1,16 @@
(function () {
const AppItem = () => {
return <div>child</div>;
};
class App extends React.Component {
render() {
return (
<div>
<p>Parent</p>
<AppItem />
</div>
);
}
}
});

View File

@ -0,0 +1,20 @@
var _ref = <div>child</div>;
var _ref3 = <p>Parent</p>;
(function () {
const AppItem = () => {
return _ref;
};
var _ref2 = <div>
{_ref3}
<AppItem />
</div>;
class App extends React.Component {
render() {
return _ref2;
}
}
});

View File

@ -0,0 +1,14 @@
export default class App extends React.Component {
render() {
return (
<div>
<p>Parent</p>
<AppItem />
</div>
);
}
}
const AppItem = () => {
return <div>child</div>;
};

View File

@ -0,0 +1,16 @@
var _ref = <p>Parent</p>;
export default class App extends React.Component {
render() {
return <div>
{_ref}
<AppItem />
</div>;
}
}
var _ref2 = <div>child</div>;
const AppItem = () => {
return _ref2;
};

View File

@ -61,6 +61,36 @@ export default class PathHoister {
}
getAttachmentPath() {
let path = this._getAttachmentPath();
if (!path) return;
let targetScope = path.scope;
// don't allow paths that have their own lexical environments to pollute
if (targetScope.path === path) {
targetScope = path.scope.parent;
}
// avoid hoisting to a scope that contains bindings that are executed after our attachment path
if (targetScope.path.isProgram() || targetScope.path.isFunction()) {
for (let name in this.bindings) {
// check binding is a direct child of this paths scope
if (!targetScope.hasOwnBinding(name)) continue;
let binding = this.bindings[name];
// allow parameter references
if (binding.kind === "param") continue;
// if this binding appears after our attachment point then don't hoist it
if (binding.path.getStatementParent().key > path.key) return;
}
}
return path;
}
_getAttachmentPath() {
let scopes = this.scopes;
let scope = scopes.pop();
@ -112,8 +142,8 @@ export default class PathHoister {
// don't bother hoisting to the same function as this will cause multiple branches to be evaluated more than once leading to a bad optimisation
if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return;
// generate declaration and insert it to our point
let uid = attachTo.scope.generateUidIdentifier("ref");
attachTo.insertBefore([
t.variableDeclaration("var", [
t.variableDeclarator(uid, this.path.node)
@ -121,7 +151,6 @@ export default class PathHoister {
]);
let parent = this.path.parentPath;
if (parent.isJSXElement() && this.path.container === parent.node.children) {
// turning the `span` in `<div><span /></div>` to an expression so we need to wrap it with
// an expression container