mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Infer render helpers for additional validation
We currently assume that any functions passes as props may be event handlers or effect functions, and thus don't check for side effects such as mutating globals. However, if a prop is a function that returns JSX that is a sure sign that it's actually a render helper and not an event handler or effect function. So we now emit a `Render` effect for any prop that is a JSX-returning function, triggering all of our render validation. This required a small fix to InferTypes: we weren't correctly populating the `return` type of function types during unification. I also improved the printing of types so we can see the inferred return types.
This commit is contained in:
@@ -893,7 +893,8 @@ export function printType(type: Type): string {
|
||||
if (type.kind === 'Object' && type.shapeId != null) {
|
||||
return `:T${type.kind}<${type.shapeId}>`;
|
||||
} else if (type.kind === 'Function' && type.shapeId != null) {
|
||||
return `:T${type.kind}<${type.shapeId}>`;
|
||||
const returnType = printType(type.return);
|
||||
return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? returnType : ''}`;
|
||||
} else {
|
||||
return `:T${type.kind}`;
|
||||
}
|
||||
|
||||
+14
@@ -25,6 +25,7 @@ import {
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
isArrayType,
|
||||
isJsxType,
|
||||
isMapType,
|
||||
isPrimitiveType,
|
||||
isRefOrRefValue,
|
||||
@@ -1842,6 +1843,19 @@ function computeSignatureForInstruction(
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const prop of value.props) {
|
||||
if (
|
||||
prop.kind === 'JsxAttribute' &&
|
||||
prop.place.identifier.type.kind === 'Function' &&
|
||||
isJsxType(prop.place.identifier.type.return)
|
||||
) {
|
||||
// Any props which return jsx are assumed to be called during render
|
||||
effects.push({
|
||||
kind: 'Render',
|
||||
place: prop.place,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -712,6 +712,15 @@ class Unifier {
|
||||
return {kind: 'Phi', operands: type.operands.map(o => this.get(o))};
|
||||
}
|
||||
|
||||
if (type.kind === 'Function') {
|
||||
return {
|
||||
kind: 'Function',
|
||||
isConstructor: type.isConstructor,
|
||||
shapeId: type.shapeId,
|
||||
return: this.get(type.return),
|
||||
};
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const renderItem = item => {
|
||||
// Normally we assume that it's safe to mutate globals in a function passed
|
||||
// as a prop, because the prop could be used as an event handler or effect.
|
||||
// But if the function returns JSX we can assume it's a render helper, ie
|
||||
// called during render, and thus it's unsafe to mutate globals or call
|
||||
// other impure code.
|
||||
global.property = true;
|
||||
return <Item item={item} value={rand} />;
|
||||
};
|
||||
return <ItemList renderItem={renderItem} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
6 | // called during render, and thus it's unsafe to mutate globals or call
|
||||
7 | // other impure code.
|
||||
> 8 | global.property = true;
|
||||
| ^^^^^^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (8:8)
|
||||
9 | return <Item item={item} value={rand} />;
|
||||
10 | };
|
||||
11 | return <ItemList renderItem={renderItem} />;
|
||||
```
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
function Component() {
|
||||
const renderItem = item => {
|
||||
// Normally we assume that it's safe to mutate globals in a function passed
|
||||
// as a prop, because the prop could be used as an event handler or effect.
|
||||
// But if the function returns JSX we can assume it's a render helper, ie
|
||||
// called during render, and thus it's unsafe to mutate globals or call
|
||||
// other impure code.
|
||||
global.property = true;
|
||||
return <Item item={item} value={rand} />;
|
||||
};
|
||||
return <ItemList renderItem={renderItem} />;
|
||||
}
|
||||
Reference in New Issue
Block a user