[autodeps] Do not include nonreactive refs or setStates in inferred deps (#32236)

This commit is contained in:
Jordan Brown 2025-03-03 15:26:57 -05:00 committed by GitHub
parent bdce84a539
commit d4e24b349e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 301 additions and 3 deletions

View File

@ -15,6 +15,8 @@ import {
ReactiveScopeDependency,
Place,
ReactiveScopeDependencies,
isUseRefType,
isSetStateType,
} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {
@ -181,11 +183,20 @@ export function inferEffectDependencies(fn: HIRFunction): void {
/**
* Step 1: push dependencies to the effect deps array
*
* Note that it's invalid to prune non-reactive deps in this pass, see
* Note that it's invalid to prune all non-reactive deps in this pass, see
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
* explanation.
*/
for (const dep of scopeInfo.deps) {
if (
(isUseRefType(dep.identifier) ||
isSetStateType(dep.identifier)) &&
!reactiveIds.has(dep.identifier.id)
) {
// exclude non-reactive hook results, which will never be in a memo block
continue;
}
const {place, instructions} = writeDependencyToInstructions(
dep,
reactiveIds.has(dep.identifier.id),

View File

@ -0,0 +1,49 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
function useCustomRef() {
const ref = useRef();
return ref;
}
function NonReactiveWrapper() {
const ref = useCustomRef();
useEffect(() => {
print(ref);
});
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef } from "react";
function useCustomRef() {
const ref = useRef();
return ref;
}
function NonReactiveWrapper() {
const $ = _c(2);
const ref = useCustomRef();
let t0;
if ($[0] !== ref) {
t0 = () => {
print(ref);
};
$[0] = ref;
$[1] = t0;
} else {
t0 = $[1];
}
useEffect(t0, [ref]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@ -0,0 +1,12 @@
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
function useCustomRef() {
const ref = useRef();
return ref;
}
function NonReactiveWrapper() {
const ref = useCustomRef();
useEffect(() => {
print(ref);
});
}

View File

@ -101,7 +101,6 @@ function Component(t0) {
useEffect(t3, [
foo,
bar,
ref,
localNonPrimitiveReactive,
localNonPrimitiveNonreactive,
]);

View File

@ -42,7 +42,7 @@ function NonReactiveRefInEffect() {
} else {
t0 = $[0];
}
useEffect(t0, [ref]);
useEffect(t0, []);
}
```

View File

@ -0,0 +1,51 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveSetStateInEffect() {
const [_, setState] = useState('initial value');
useEffect(() => print(setState));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useState } from "react";
import { print } from "shared-runtime";
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveSetStateInEffect() {
const $ = _c(1);
const [, setState] = useState("initial value");
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => print(setState);
$[0] = t0;
} else {
t0 = $[0];
}
useEffect(t0, []);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@ -0,0 +1,14 @@
// @inferEffectDependencies
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveSetStateInEffect() {
const [_, setState] = useState('initial value');
useEffect(() => print(setState));
}

View File

@ -0,0 +1,66 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
/*
* Ref types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const ref1 = useRef('initial value');
const ref2 = useRef('initial value');
let ref;
if (props.foo) {
ref = ref1;
} else {
ref = ref2;
}
useEffect(() => print(ref));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
/*
* Ref types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const $ = _c(4);
const ref1 = useRef("initial value");
const ref2 = useRef("initial value");
let ref;
if ($[0] !== props.foo) {
if (props.foo) {
ref = ref1;
} else {
ref = ref2;
}
$[0] = props.foo;
$[1] = ref;
} else {
ref = $[1];
}
let t0;
if ($[2] !== ref) {
t0 = () => print(ref);
$[2] = ref;
$[3] = t0;
} else {
t0 = $[3];
}
useEffect(t0, [ref]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@ -0,0 +1,18 @@
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
/*
* Ref types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const ref1 = useRef('initial value');
const ref2 = useRef('initial value');
let ref;
if (props.foo) {
ref = ref1;
} else {
ref = ref2;
}
useEffect(() => print(ref));
}

View File

@ -0,0 +1,60 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const [_state1, setState1] = useRef('initial value');
const [_state2, setState2] = useRef('initial value');
let setState;
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
useEffect(() => print(setState));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useState } from "react";
import { print } from "shared-runtime";
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const $ = _c(2);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
let t0;
if ($[0] !== setState) {
t0 = () => print(setState);
$[0] = setState;
$[1] = t0;
} else {
t0 = $[1];
}
useEffect(t0, [setState]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@ -0,0 +1,18 @@
// @inferEffectDependencies
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const [_state1, setState1] = useRef('initial value');
const [_state2, setState2] = useRef('initial value');
let setState;
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
useEffect(() => print(setState));
}