mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Fewer assumptions about nonmutability when change detection enabled
Summary: Change detection is desgined to determine whether rules of react have been violated, and to do so better we need to loosen Forgets assumptions about what kinds of values don't need to be memoized. For example, the compiler typically doesn't think of `o.x` as something that needs to be memoized, because it does not allocate. However, we want to compare `o.x` in the current render with `o.x` in a previous one, so we now insert a "memoization" (comparison, really) block around this value.
ghstack-source-id: 413d866dde
Pull Request resolved: https://github.com/facebook/react/pull/30180
This commit is contained in:
@@ -269,6 +269,7 @@ function* runWithEnvironment(
|
||||
|
||||
if (
|
||||
env.config.enablePreserveExistingManualUseMemo === "scope" ||
|
||||
env.config.enableChangeDetectionForDebugging != null ||
|
||||
env.config.disableMemoizationForDebugging
|
||||
) {
|
||||
memoizeExistingUseMemos(hir);
|
||||
|
||||
+13
@@ -653,6 +653,7 @@ function codegenReactiveScope(
|
||||
const cacheLoadOldValueStatements: Array<t.Statement> = [];
|
||||
const changeDetectionStatements: Array<t.Statement> = [];
|
||||
const idempotenceDetectionStatements: Array<t.Statement> = [];
|
||||
const restoreOldValueStatements: Array<t.Statement> = [];
|
||||
|
||||
for (const { name, index, value } of cacheLoads) {
|
||||
const loadName = cx.synthesizeName(`old$${name.name}`);
|
||||
@@ -669,6 +670,17 @@ function codegenReactiveScope(
|
||||
t.variableDeclarator(t.identifier(loadName), slot),
|
||||
])
|
||||
);
|
||||
if (scope.source) {
|
||||
restoreOldValueStatements.push(
|
||||
t.expressionStatement(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.cloneNode(name, true),
|
||||
t.identifier(loadName)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
changeDetectionStatements.push(
|
||||
t.expressionStatement(
|
||||
t.callExpression(t.identifier(detectionFunction), [
|
||||
@@ -709,6 +721,7 @@ function codegenReactiveScope(
|
||||
t.blockStatement([
|
||||
...cacheLoadOldValueStatements,
|
||||
...changeDetectionStatements,
|
||||
...restoreOldValueStatements,
|
||||
])
|
||||
),
|
||||
...cacheStoreStatements,
|
||||
|
||||
+5
-1
@@ -366,7 +366,11 @@ export function findDisjointMutableValues(
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
const operands = collectMutableOperands(fn, instr, false);
|
||||
const operands = collectMutableOperands(
|
||||
fn,
|
||||
instr,
|
||||
fn.env.config.enableChangeDetectionForDebugging != null
|
||||
);
|
||||
if (operands.length !== 0) {
|
||||
scopeIdentifiers.union(operands);
|
||||
}
|
||||
|
||||
+3
-1
@@ -811,7 +811,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
this.env = env;
|
||||
this.options = {
|
||||
memoizeJsxElements: !this.env.config.enableForest,
|
||||
forceMemoizePrimitives: this.env.config.enableForest,
|
||||
forceMemoizePrimitives:
|
||||
this.env.config.enableForest ||
|
||||
this.env.config.enableChangeDetectionForDebugging != null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+45
-13
@@ -39,10 +39,10 @@ function useOther(x) {
|
||||
}
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(8);
|
||||
let t0;
|
||||
{
|
||||
t0 = f(props.x);
|
||||
t0 = props.x;
|
||||
let condition = $[0] !== props.x;
|
||||
if (!condition) {
|
||||
let old$t0 = $[1];
|
||||
@@ -51,31 +51,63 @@ function Component(props) {
|
||||
$[0] = props.x;
|
||||
$[1] = t0;
|
||||
if (condition) {
|
||||
t0 = f(props.x);
|
||||
t0 = props.x;
|
||||
$structuralCheck($[1], t0, "t0", "Component", "recomputed", "(8:8)");
|
||||
t0 = $[1];
|
||||
}
|
||||
}
|
||||
const w = t0;
|
||||
const z = useOther(w);
|
||||
const [x] = useState(z);
|
||||
let t1;
|
||||
{
|
||||
t1 = <div>{x}</div>;
|
||||
let condition = $[2] !== x;
|
||||
t1 = f(t0);
|
||||
let condition = $[2] !== t0;
|
||||
if (!condition) {
|
||||
let old$t1 = $[3];
|
||||
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(11:11)");
|
||||
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(8:8)");
|
||||
}
|
||||
$[2] = x;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
if (condition) {
|
||||
t1 = <div>{x}</div>;
|
||||
$structuralCheck($[3], t1, "t1", "Component", "recomputed", "(11:11)");
|
||||
t1 = f(t0);
|
||||
$structuralCheck($[3], t1, "t1", "Component", "recomputed", "(8:8)");
|
||||
t1 = $[3];
|
||||
}
|
||||
}
|
||||
return t1;
|
||||
const w = t1;
|
||||
const z = useOther(w);
|
||||
const t2 = useState(z);
|
||||
let x;
|
||||
{
|
||||
[x] = t2;
|
||||
let condition = $[4] !== t2;
|
||||
if (!condition) {
|
||||
let old$x = $[5];
|
||||
$structuralCheck(old$x, x, "x", "Component", "cached", "(10:10)");
|
||||
}
|
||||
$[4] = t2;
|
||||
$[5] = x;
|
||||
if (condition) {
|
||||
[x] = t2;
|
||||
$structuralCheck($[5], x, "x", "Component", "recomputed", "(10:10)");
|
||||
x = $[5];
|
||||
}
|
||||
}
|
||||
let t3;
|
||||
{
|
||||
t3 = <div>{x}</div>;
|
||||
let condition = $[6] !== x;
|
||||
if (!condition) {
|
||||
let old$t3 = $[7];
|
||||
$structuralCheck(old$t3, t3, "t3", "Component", "cached", "(11:11)");
|
||||
}
|
||||
$[6] = x;
|
||||
$[7] = t3;
|
||||
if (condition) {
|
||||
t3 = <div>{x}</div>;
|
||||
$structuralCheck($[7], t3, "t3", "Component", "recomputed", "(11:11)");
|
||||
t3 = $[7];
|
||||
}
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
function f(x) {
|
||||
|
||||
+36
-13
@@ -20,32 +20,55 @@ import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDe
|
||||
import { useState } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = f(props.x);
|
||||
t0 = props.x;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const [x] = useState(t0);
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = f(t0);
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const t2 = useState(t1);
|
||||
let x;
|
||||
{
|
||||
t1 = <div>{x}</div>;
|
||||
let condition = $[1] !== x;
|
||||
[x] = t2;
|
||||
let condition = $[2] !== t2;
|
||||
if (!condition) {
|
||||
let old$t1 = $[2];
|
||||
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(6:6)");
|
||||
let old$x = $[3];
|
||||
$structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)");
|
||||
}
|
||||
$[1] = x;
|
||||
$[2] = t1;
|
||||
$[2] = t2;
|
||||
$[3] = x;
|
||||
if (condition) {
|
||||
t1 = <div>{x}</div>;
|
||||
$structuralCheck($[2], t1, "t1", "Component", "recomputed", "(6:6)");
|
||||
t1 = $[2];
|
||||
[x] = t2;
|
||||
$structuralCheck($[3], x, "x", "Component", "recomputed", "(5:5)");
|
||||
x = $[3];
|
||||
}
|
||||
}
|
||||
return t1;
|
||||
let t3;
|
||||
{
|
||||
t3 = <div>{x}</div>;
|
||||
let condition = $[4] !== x;
|
||||
if (!condition) {
|
||||
let old$t3 = $[5];
|
||||
$structuralCheck(old$t3, t3, "t3", "Component", "cached", "(6:6)");
|
||||
}
|
||||
$[4] = x;
|
||||
$[5] = t3;
|
||||
if (condition) {
|
||||
t3 = <div>{x}</div>;
|
||||
$structuralCheck($[5], t3, "t3", "Component", "recomputed", "(6:6)");
|
||||
t3 = $[5];
|
||||
}
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+48
-16
@@ -35,10 +35,10 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useState } from "react"; // @enableChangeDetectionForDebugging
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(9);
|
||||
let t0;
|
||||
{
|
||||
t0 = f(props.x);
|
||||
t0 = props.x;
|
||||
let condition = $[0] !== props.x;
|
||||
if (!condition) {
|
||||
let old$t0 = $[1];
|
||||
@@ -47,41 +47,73 @@ function Component(props) {
|
||||
$[0] = props.x;
|
||||
$[1] = t0;
|
||||
if (condition) {
|
||||
t0 = f(props.x);
|
||||
t0 = props.x;
|
||||
$structuralCheck($[1], t0, "t0", "Component", "recomputed", "(4:4)");
|
||||
t0 = $[1];
|
||||
}
|
||||
}
|
||||
const w = t0;
|
||||
const [x] = useState(w);
|
||||
let t1;
|
||||
{
|
||||
t1 = (
|
||||
t1 = f(t0);
|
||||
let condition = $[2] !== t0;
|
||||
if (!condition) {
|
||||
let old$t1 = $[3];
|
||||
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(4:4)");
|
||||
}
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
if (condition) {
|
||||
t1 = f(t0);
|
||||
$structuralCheck($[3], t1, "t1", "Component", "recomputed", "(4:4)");
|
||||
t1 = $[3];
|
||||
}
|
||||
}
|
||||
const w = t1;
|
||||
const t2 = useState(w);
|
||||
let x;
|
||||
{
|
||||
[x] = t2;
|
||||
let condition = $[4] !== t2;
|
||||
if (!condition) {
|
||||
let old$x = $[5];
|
||||
$structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)");
|
||||
}
|
||||
$[4] = t2;
|
||||
$[5] = x;
|
||||
if (condition) {
|
||||
[x] = t2;
|
||||
$structuralCheck($[5], x, "x", "Component", "recomputed", "(5:5)");
|
||||
x = $[5];
|
||||
}
|
||||
}
|
||||
let t3;
|
||||
{
|
||||
t3 = (
|
||||
<div>
|
||||
{x}
|
||||
{w}
|
||||
</div>
|
||||
);
|
||||
let condition = $[2] !== x || $[3] !== w;
|
||||
let condition = $[6] !== x || $[7] !== w;
|
||||
if (!condition) {
|
||||
let old$t1 = $[4];
|
||||
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)");
|
||||
let old$t3 = $[8];
|
||||
$structuralCheck(old$t3, t3, "t3", "Component", "cached", "(7:10)");
|
||||
}
|
||||
$[2] = x;
|
||||
$[3] = w;
|
||||
$[4] = t1;
|
||||
$[6] = x;
|
||||
$[7] = w;
|
||||
$[8] = t3;
|
||||
if (condition) {
|
||||
t1 = (
|
||||
t3 = (
|
||||
<div>
|
||||
{x}
|
||||
{w}
|
||||
</div>
|
||||
);
|
||||
$structuralCheck($[4], t1, "t1", "Component", "recomputed", "(7:10)");
|
||||
t1 = $[4];
|
||||
$structuralCheck($[8], t3, "t3", "Component", "recomputed", "(7:10)");
|
||||
t3 = $[8];
|
||||
}
|
||||
}
|
||||
return t1;
|
||||
return t3;
|
||||
}
|
||||
|
||||
function f(x) {
|
||||
|
||||
Reference in New Issue
Block a user