mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Support hooks/methods on the React namespace
Allows using hooks/methods off of the `React` namespace, for example `React.useState(sathya)`. Thanks to the previous PR we correctly handle things like validation of hooks called via propertyload syntax. The main change here is to teach the compiler about the `React` namespace. This is a bit of a hack since we treat it as a global, but we're transforming React code so this seems reasonable (?). There are a few additional touch-ups which I'll do in subsequent PRs to make review easier. For example, we need to teach our useMemo/useCallback flattening logic to also handle the case of `React.useMemo()` etc.
This commit is contained in:
@@ -2660,20 +2660,6 @@ function lowerMemberExpression(
|
||||
const object =
|
||||
loweredObject ?? lowerExpressionToTemporary(builder, objectNode);
|
||||
|
||||
if (objectNode.isIdentifier() && objectNode.node.name === "React") {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerMemberExpression) Handle loading properties from React namespace`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: propertyNode.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
return {
|
||||
object,
|
||||
property: propertyNode.toString(),
|
||||
value: { kind: "UnsupportedNode", node: exprNode, loc: exprLoc },
|
||||
};
|
||||
}
|
||||
|
||||
if (!expr.node.computed) {
|
||||
if (!propertyNode.isIdentifier()) {
|
||||
builder.errors.push({
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
addHook,
|
||||
addObject,
|
||||
} from "./ObjectShape";
|
||||
import { BuiltInType, FunctionType, PolyType } from "./Types";
|
||||
import { BuiltInType, PolyType } from "./Types";
|
||||
|
||||
/**
|
||||
* This file exports types and defaults for JavaScript global objects.
|
||||
@@ -234,7 +234,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
|
||||
// TODO(mofeiZ): We currently only store rest param effects for hooks
|
||||
// until FeatureFlag `enableTreatHooksAsFunctions` is removed
|
||||
const BUILTIN_HOOKS: Array<[string, FunctionType]> = [
|
||||
const BUILTIN_HOOKS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
"useContext",
|
||||
addHook(DEFAULT_SHAPES, [], {
|
||||
@@ -314,6 +314,8 @@ const BUILTIN_HOOKS: Array<[string, FunctionType]> = [
|
||||
],
|
||||
];
|
||||
|
||||
TYPED_GLOBALS.push(["React", addObject(DEFAULT_SHAPES, null, BUILTIN_HOOKS)]);
|
||||
|
||||
export type Global = BuiltInType | PolyType;
|
||||
export type GlobalRegistry = Map<string, Global>;
|
||||
export const DEFAULT_GLOBALS: GlobalRegistry = new Map(BUILTIN_HOOKS);
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import * as React from "react";
|
||||
import { calculateExpensiveNumber } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return <div>{expensiveNumber}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import * as React from "react";
|
||||
import { calculateExpensiveNumber } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(5);
|
||||
const [x] = React.useState(0);
|
||||
const c_0 = $[0] !== x;
|
||||
let t0;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
t0 = () => calculateExpensiveNumber(x);
|
||||
t1 = [x];
|
||||
$[0] = x;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
const expensiveNumber = React.useMemo(t0, t1);
|
||||
const c_3 = $[3] !== expensiveNumber;
|
||||
let t2;
|
||||
if (c_3) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
$[3] = expensiveNumber;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+5
-6
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { calculateExpensiveNumber } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const [x] = React.useState(0);
|
||||
@@ -7,9 +8,7 @@ function Component(props) {
|
||||
return <div>{expensiveNumber}</div>;
|
||||
}
|
||||
|
||||
function Component2(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return <div>{expensiveNumber}</div>;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import * as React from "react";
|
||||
|
||||
function Component(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return <div>{expensiveNumber}</div>;
|
||||
}
|
||||
|
||||
function Component2(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return <div>{expensiveNumber}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: (BuildHIR::lowerMemberExpression) Handle loading properties from React namespace (4:4)
|
||||
|
||||
[ReactForget] Todo: (BuildHIR::lowerMemberExpression) Handle loading properties from React namespace (5:5)
|
||||
```
|
||||
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Foo() {
|
||||
const [x, setX] = React.useState(1);
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: (BuildHIR::lowerMemberExpression) Handle loading properties from React namespace (2:2)
|
||||
```
|
||||
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
function Foo() {
|
||||
const [x, setX] = React.useState(1);
|
||||
return x;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const [x, setX] = React.useState(1);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const [x] = React.useState(1);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
function Component() {
|
||||
const [x, setX] = React.useState(1);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
const FooContext = React.createContext({ current: null });
|
||||
|
||||
function Component(props) {
|
||||
const foo = React.useContext(FooContext);
|
||||
const ref = React.useRef();
|
||||
const [x, setX] = React.useState(false);
|
||||
const onClick = () => {
|
||||
setX(true);
|
||||
ref.current = true;
|
||||
foo.current = true;
|
||||
};
|
||||
return <div onClick={onClick}>{React.cloneElement(props.children)}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ children: <div>Hello</div> }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
const FooContext = React.createContext({ current: null });
|
||||
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(7);
|
||||
const foo = React.useContext(FooContext);
|
||||
const ref = React.useRef();
|
||||
const [x, setX] = React.useState(false);
|
||||
const c_0 = $[0] !== foo.current;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = () => {
|
||||
setX(true);
|
||||
ref.current = true;
|
||||
foo.current = true;
|
||||
};
|
||||
$[0] = foo.current;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const onClick = t0;
|
||||
const c_2 = $[2] !== props.children;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
t1 = React.cloneElement(props.children);
|
||||
$[2] = props.children;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const c_4 = $[4] !== onClick;
|
||||
const c_5 = $[5] !== t1;
|
||||
let t2;
|
||||
if (c_4 || c_5) {
|
||||
t2 = <div onClick={onClick}>{t1}</div>;
|
||||
$[4] = onClick;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ children: <div>Hello</div> }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
const FooContext = React.createContext({ current: null });
|
||||
|
||||
function Component(props) {
|
||||
const foo = React.useContext(FooContext);
|
||||
const ref = React.useRef();
|
||||
const [x, setX] = React.useState(false);
|
||||
const onClick = () => {
|
||||
setX(true);
|
||||
ref.current = true;
|
||||
foo.current = true;
|
||||
};
|
||||
return <div onClick={onClick}>{React.cloneElement(props.children)}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ children: <div>Hello</div> }],
|
||||
};
|
||||
@@ -54,6 +54,10 @@ export function getNumber(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
export function calculateExpensiveNumber(x: number): number {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions that do not mutate their parameters
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user