mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Allow context values to be mutated in callbacks
Adds a new type for representing context values, which is transitive. So `useContext(a).b.c` also gets inferred as a context type. This allows us to refine our inference, and allow passing callbacks that modify context where a "frozen" lambda is exepcted.
This commit is contained in:
@@ -9,6 +9,7 @@ import { Effect, ValueKind } from "./HIR";
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInContextId,
|
||||
BuiltInUseRefId,
|
||||
BuiltInUseStateId,
|
||||
ShapeRegistry,
|
||||
@@ -239,7 +240,7 @@ const BUILTIN_HOOKS: Array<[string, FunctionType]> = [
|
||||
addHook(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: { kind: "Object", shapeId: BuiltInContextId },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "useContext",
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
|
||||
@@ -1111,6 +1111,10 @@ export function isSetStateType(id: Identifier): boolean {
|
||||
return id.type.kind === "Function" && id.type.shapeId === "BuiltInSetState";
|
||||
}
|
||||
|
||||
export function isContextType(id: Identifier): boolean {
|
||||
return id.type.kind === "Object" && id.type.shapeId === "BuiltInContext";
|
||||
}
|
||||
|
||||
export function getHookKind(env: Environment, id: Identifier): HookKind | null {
|
||||
const idType = id.type;
|
||||
if (idType.kind === "Function") {
|
||||
|
||||
@@ -170,6 +170,7 @@ export const BuiltInUseStateId = "BuiltInUseState";
|
||||
export const BuiltInSetStateId = "BuiltInSetState";
|
||||
export const BuiltInUseRefId = "BuiltInUseRefId";
|
||||
export const BuiltInRefValueId = "BuiltInRefValue";
|
||||
export const BuiltInContextId = "BuiltInContext";
|
||||
export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";
|
||||
|
||||
/**
|
||||
@@ -293,6 +294,10 @@ addObject(BUILTIN_SHAPES, BuiltInUseRefId, [
|
||||
["current", { kind: "Object", shapeId: BuiltInRefValueId }],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInContextId, [
|
||||
["*", { kind: "Object", shapeId: BuiltInContextId }],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInRefValueId, []);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Effect,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
isContextType,
|
||||
isRefValueType,
|
||||
isSetStateType,
|
||||
isUseRefType,
|
||||
@@ -142,7 +143,8 @@ function infer(
|
||||
if (
|
||||
isUseRefType(dep.identifier) ||
|
||||
isRefValueType(dep.identifier) ||
|
||||
isSetStateType(dep.identifier)
|
||||
isSetStateType(dep.identifier) ||
|
||||
isContextType(dep.identifier)
|
||||
) {
|
||||
// TODO: this is a hack to ensure we treat functions which reference refs
|
||||
// as having a capture and therefore being considered mutable. this ensures
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @debug
|
||||
function Component(props) {
|
||||
const FooContext = useContext(Foo);
|
||||
const onClick = () => {
|
||||
FooContext.current = true;
|
||||
};
|
||||
return <div onClick={onClick} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(4);
|
||||
const FooContext = useContext(Foo);
|
||||
const c_0 = $[0] !== FooContext.current;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = () => {
|
||||
FooContext.current = true;
|
||||
};
|
||||
$[0] = FooContext.current;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const onClick = t0;
|
||||
const c_2 = $[2] !== onClick;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
t1 = <div onClick={onClick} />;
|
||||
$[2] = onClick;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @debug
|
||||
function Component(props) {
|
||||
const FooContext = useContext(Foo);
|
||||
const onClick = () => {
|
||||
FooContext.current = true;
|
||||
};
|
||||
return <div onClick={onClick} />;
|
||||
}
|
||||
@@ -460,6 +460,7 @@ const skipFilter = new Set([
|
||||
"fbtparam-text-must-use-expression-container",
|
||||
"fbtparam-with-jsx-fragment-value",
|
||||
"fbt-preserve-jsxtext",
|
||||
"useContext-mutable-value",
|
||||
]);
|
||||
|
||||
export default skipFilter;
|
||||
|
||||
Reference in New Issue
Block a user