mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Allow ref argument to be mutated
Previously, Forget would throw if _any_ of the arguments to a component are modified. This isn't quite right as a ref argument can be modified. This PR assumes the second argument of a component to be a ref and allows it to be mutable. A future PR will add types to this argument so the validateRefAccessDuringRender can catch if ref is mutated in render. This PR contains a todo test for this.
This commit is contained in:
+70
-17
@@ -21,6 +21,7 @@ import {
|
||||
MethodCall,
|
||||
Phi,
|
||||
Place,
|
||||
SpreadPattern,
|
||||
Type,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
@@ -133,26 +134,48 @@ export default function inferReferenceEffects(
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([ValueReason.ReactiveFunctionArgument]),
|
||||
};
|
||||
for (const param of fn.params) {
|
||||
|
||||
const isComponent = isComponentName(fn.id);
|
||||
if (isComponent) {
|
||||
CompilerError.invariant(fn.params.length <= 2, {
|
||||
reason:
|
||||
"Expected React component to have not more than two parameters: one for props and for ref",
|
||||
description: null,
|
||||
loc: fn.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
const [props, ref] = fn.params;
|
||||
let value: InstructionValue;
|
||||
let place: Place;
|
||||
if (param.kind === "Identifier") {
|
||||
place = param;
|
||||
value = {
|
||||
kind: "Primitive",
|
||||
loc: param.loc,
|
||||
value: undefined,
|
||||
};
|
||||
} else {
|
||||
place = param.place;
|
||||
value = {
|
||||
kind: "Primitive",
|
||||
loc: param.place.loc,
|
||||
value: undefined,
|
||||
};
|
||||
if (props) {
|
||||
inferParam(props, initialState, paramKind);
|
||||
}
|
||||
if (ref) {
|
||||
if (ref.kind === "Identifier") {
|
||||
place = ref;
|
||||
value = {
|
||||
kind: "ObjectExpression",
|
||||
properties: [],
|
||||
loc: ref.loc,
|
||||
};
|
||||
} else {
|
||||
place = ref.place;
|
||||
value = {
|
||||
kind: "ObjectExpression",
|
||||
properties: [],
|
||||
loc: ref.place.loc,
|
||||
};
|
||||
}
|
||||
initialState.initialize(value, {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
});
|
||||
initialState.define(place, value);
|
||||
}
|
||||
} else {
|
||||
for (const param of fn.params) {
|
||||
inferParam(param, initialState, paramKind);
|
||||
}
|
||||
initialState.initialize(value, paramKind);
|
||||
initialState.define(place, value);
|
||||
}
|
||||
|
||||
// Map of blocks to the last (merged) incoming state that was processed
|
||||
@@ -596,6 +619,36 @@ class InferenceState {
|
||||
}
|
||||
}
|
||||
|
||||
function isComponentName(name: string | null): boolean {
|
||||
return name !== null && /^[A-Z]/.test(name);
|
||||
}
|
||||
|
||||
function inferParam(
|
||||
param: Place | SpreadPattern,
|
||||
initialState: InferenceState,
|
||||
paramKind: AbstractValue
|
||||
): void {
|
||||
let value: InstructionValue;
|
||||
let place: Place;
|
||||
if (param.kind === "Identifier") {
|
||||
place = param;
|
||||
value = {
|
||||
kind: "Primitive",
|
||||
loc: param.loc,
|
||||
value: undefined,
|
||||
};
|
||||
} else {
|
||||
place = param.place;
|
||||
value = {
|
||||
kind: "Primitive",
|
||||
loc: param.place.loc,
|
||||
value: undefined,
|
||||
};
|
||||
}
|
||||
initialState.initialize(value, paramKind);
|
||||
initialState.define(place, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Joins two values using the following rules:
|
||||
* == Effect Transitions ==
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useEffect } from "react";
|
||||
|
||||
function Foo(props, ref) {
|
||||
useEffect(() => {
|
||||
ref.current = 2;
|
||||
}, []);
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useEffect, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
function Foo(props, ref) {
|
||||
const $ = useMemoCache(5);
|
||||
let t0;
|
||||
if ($[0] !== ref.current) {
|
||||
t0 = () => {
|
||||
ref.current = 2;
|
||||
};
|
||||
$[0] = ref.current;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[3] !== props.bar) {
|
||||
t2 = <div>{props.bar}</div>;
|
||||
$[3] = props.bar;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>foo</div>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
function Foo(props, ref) {
|
||||
useEffect(() => {
|
||||
ref.current = 2;
|
||||
}, []);
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender: true
|
||||
function Foo(props, ref) {
|
||||
ref.current = 2;
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender: true
|
||||
function Foo(props, ref) {
|
||||
const $ = useMemoCache(2);
|
||||
ref.current = 2;
|
||||
let t0;
|
||||
if ($[0] !== props.bar) {
|
||||
t0 = <div>{props.bar}</div>;
|
||||
$[0] = props.bar;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>foo</div>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// @validateRefAccessDuringRender: true
|
||||
function Foo(props, ref) {
|
||||
ref.current = 2;
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
|
||||
isComponent: true,
|
||||
};
|
||||
+8
-8
@@ -2,8 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @debug
|
||||
function Component(a, [b], { c }) {
|
||||
function Component({ a: a, b: [b], c: { c } }) {
|
||||
let d = a++;
|
||||
let e = ++a;
|
||||
let f = b--;
|
||||
@@ -15,7 +14,7 @@ function Component(a, [b], { c }) {
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2, [3], { c: 4 }],
|
||||
params: [{ a: 2, b: [3], c: { c: 4 } }],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
@@ -24,11 +23,12 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
|
||||
function Component(a, t37, t38) {
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(t38) {
|
||||
const $ = useMemoCache(10);
|
||||
let [b] = t37;
|
||||
let { c } = t38;
|
||||
let { a, b: t40, c: t41 } = t38;
|
||||
let [b] = t40;
|
||||
let { c } = t41;
|
||||
const d = a++;
|
||||
const e = ++a;
|
||||
const f = b--;
|
||||
@@ -66,7 +66,7 @@ function Component(a, t37, t38) {
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2, [3], { c: 4 }],
|
||||
params: [{ a: 2, b: [3], c: { c: 4 } }],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
// @debug
|
||||
function Component(a, [b], { c }) {
|
||||
function Component({ a: a, b: [b], c: { c } }) {
|
||||
let d = a++;
|
||||
let e = ++a;
|
||||
let f = b--;
|
||||
@@ -11,6 +10,6 @@ function Component(a, [b], { c }) {
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2, [3], { c: 4 }],
|
||||
params: [{ a: 2, b: [3], c: { c: 4 } }],
|
||||
isComponent: false,
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(a) {
|
||||
let d = a++;
|
||||
let e = ++a;
|
||||
return [a, d, e];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(a) {
|
||||
const $ = useMemoCache(4);
|
||||
const d = a++;
|
||||
const e = ++a;
|
||||
let t0;
|
||||
if ($[0] !== a || $[1] !== d || $[2] !== e) {
|
||||
t0 = [a, d, e];
|
||||
$[0] = a;
|
||||
$[1] = d;
|
||||
$[2] = e;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [4,2,4]
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
function Component(a) {
|
||||
let d = a++;
|
||||
let e = ++a;
|
||||
return [a, d, e];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [2],
|
||||
isComponent: false,
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component({ c }) {
|
||||
let h = c++;
|
||||
let i = --c;
|
||||
return [c, h, i];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ c: 4 }],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(t14) {
|
||||
const $ = useMemoCache(4);
|
||||
let { c } = t14;
|
||||
const h = c++;
|
||||
const i = --c;
|
||||
let t0;
|
||||
if ($[0] !== c || $[1] !== h || $[2] !== i) {
|
||||
t0 = [c, h, i];
|
||||
$[0] = c;
|
||||
$[1] = h;
|
||||
$[2] = i;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ c: 4 }],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [4,4,4]
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
function Component({ c }) {
|
||||
let h = c++;
|
||||
let i = --c;
|
||||
return [c, h, i];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ c: 4 }],
|
||||
isComponent: false,
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component([b]) {
|
||||
let f = b--;
|
||||
let g = --b;
|
||||
return [b, f, g];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [[3]],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(t14) {
|
||||
const $ = useMemoCache(4);
|
||||
let [b] = t14;
|
||||
const f = b--;
|
||||
const g = --b;
|
||||
let t0;
|
||||
if ($[0] !== b || $[1] !== f || $[2] !== g) {
|
||||
t0 = [b, f, g];
|
||||
$[0] = b;
|
||||
$[1] = f;
|
||||
$[2] = g;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [[3]],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [1,3,1]
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
function Component([b]) {
|
||||
let f = b--;
|
||||
let g = --b;
|
||||
return [b, f, g];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [[3]],
|
||||
isComponent: false,
|
||||
};
|
||||
Reference in New Issue
Block a user