mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
compiler: fixture for suboptimal jsx sibling memo block merging
React Compiler attempts to merge consecutive reactive scopes in order to reduce overhead. The basic idea is that if two consecutive scopes would always invalidate together then we should merge them. It gets more complicated, though, because values produced by the earlier scope may not always invalidate when their inputs do. For example, a scope that produces `fn(x)` may not invalidate on all changes to `x` if the function is `Math.max(x, 10)` (changing x from 8 to 9 won't change the output).
Previously we were conservative and only merged if either:
* the two scopes had the same dependencies
* the second scope's deps exactly matched the previous scope's outputs.
You can see this in the new fixture, where the second `<button>` gets its own scope, which happens because the preceding scope has an extra output that isn't a dep of the `<button>`'s scope.
ghstack-source-id: d869c8d4df
Pull Request resolved: https://github.com/facebook/react/pull/29155
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useState } from "react";
|
||||
|
||||
function Component() {
|
||||
const [count, setCount] = useState(0);
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setCount(count - 1)}>Decrement</button>
|
||||
{/**
|
||||
* The scope for the <button> depends on just the scope for the callback,
|
||||
* but the previous scope (after merging) will declare both the above
|
||||
* <button> and the callback.
|
||||
*/}
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useState } from "react";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(8);
|
||||
const [count, setCount] = useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== count) {
|
||||
t0 = <button onClick={() => setCount(count - 1)}>Decrement</button>;
|
||||
|
||||
t1 = () => setCount(count + 1);
|
||||
$[0] = count;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== t1) {
|
||||
t2 = <button onClick={t1}>Increment</button>;
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
let t3;
|
||||
if ($[5] !== t0 || $[6] !== t2) {
|
||||
t3 = (
|
||||
<div>
|
||||
{t0}
|
||||
{t2}
|
||||
</div>
|
||||
);
|
||||
$[5] = t0;
|
||||
$[6] = t2;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div><button>Decrement</button><button>Increment</button></div>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function Component() {
|
||||
const [count, setCount] = useState(0);
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setCount(count - 1)}>Decrement</button>
|
||||
{/**
|
||||
* The scope for the <button> depends on just the scope for the callback,
|
||||
* but the previous scope (after merging) will declare both the above
|
||||
* <button> and the callback.
|
||||
*/}
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
Reference in New Issue
Block a user