英文:
React props discriminative union vs conditional type
问题
以下是您要的代码部分的翻译:
I am scratching my head around why a conditional type I made for a component's props is not working, but a discriminative union is working as expected.
我正在思考为什么我为组件的属性创建的条件类型不起作用,但区分联合类型按预期工作。
The example can be found in this [Typescript playground][1]
可以在这个[Typescript playground][1]中找到示例。
Am I doing something wrong, or there is some react context which I am missing?
我是不是做错了什么,或者有一些我遗漏的React上下文吗?
A snippet from the playground is bellow:
以下是来自示例的代码片段:
import React, { useState, useRef } from "react";
import { Button, ButtonProps, IconButton, Menu } from "@neo4j-ndl/react";
import type { IconButtonProps, ContextMenuProps } from "@neo4j-ndl/react";
type ActionableType = "button" | "iconButton";
type ActionableProps<T extends ActionableType> = {
"data-testid": string;
} & T extends "button" // narrowing happens here
? Omit<ButtonProps, "onClick" | "ref" | "children"> & {
zzButtonText: ButtonProps["children"];
}
: Omit<IconButtonProps, "onClick" | "ref" | "children"> & {
zzIcon: IconButtonProps["children"];
};
type ConditionalActionableProps<T extends ActionableType> = {
actionableType: T;
actionableProps: ActionableProps<T>;
};
type ActionableWithMenuProps<T extends ActionableType> =
ConditionalActionableProps<T> & {
menuProps: Omit<
ContextMenuProps,
"open" | "onClose" | "anchorEl" | "ref" | "children"
>;
children: ContextMenuProps["children"];
};
const ConditionalComponent = <T extends ActionableType = "iconButton">({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps<T>) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuAnchorRef = useRef(null);
let actionable: JSX.Element;
if (actionableType === "iconButton") {
const { zzIcon, ...rest } = actionableProps; // why actionableProps is not narrowed
actionable = <IconButton {...rest}>{zzIcon}</IconButton>;
} else {
const { zzButtonText, ...rest } = actionableProps;
actionable = <Button {...rest}>{zzButtonText}</Button>;
}
return (
<>
{actionable}
<Menu
open={isMenuOpen && !!menuAnchorRef.current}
onClose={() => setIsMenuOpen(false)}
anchorEl={menuAnchorRef.current}
>
{children}
</Menu>
</>
);
};
<details>
<summary>英文:</summary>
I am scratching my head around why a conditional type I made for a component's props is not working, but a discriminative union is working as expected.
The example can be found in this [Typescript playground][1]
Am I doing something wrong, or there is some react context which I am missing?
A snippet from the playground is bellow:
```typescript
import React, { useState, useRef } from "react";
import { Button, ButtonProps, IconButton, Menu } from "@neo4j-ndl/react";
import type { IconButtonProps, ContextMenuProps } from "@neo4j-ndl/react";
type ActionableType = "button" | "iconButton";
type ActionableProps<T extends ActionableType> = {
"data-testid": string;
} & T extends "button" // narrowing happens here 👈
? Omit<ButtonProps, "onClick" | "ref" | "children"> & {
zzButtonText: ButtonProps["children"];
}
: Omit<IconButtonProps, "onClick" | "ref" | "children"> & {
zzIcon: IconButtonProps["children"];
};
type ConditionalActionableProps<T extends ActionableType> = {
actionableType: T;
actionableProps: ActionableProps<T>;
};
type ActionableWithMenuProps<T extends ActionableType> =
ConditionalActionableProps<T> & {
menuProps: Omit<
ContextMenuProps,
"open" | "onClose" | "anchorEl" | "ref" | "children"
>;
children: ContextMenuProps["children"];
};
const ConditionalComponent = <T extends ActionableType = "iconButton">({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps<T>) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuAnchorRef = useRef(null);
let actionable: JSX.Element;
if (actionableType === "iconButton") {
const { zzIcon, ...rest } = actionableProps; // 👈 why actionableProps is not narrowed
actionable = <IconButton {...rest}>{zzIcon}</IconButton>;
} else {
const { zzButtonText, ...rest } = actionableProps;
actionable = <Button {...rest}>{zzButtonText}</Button>;
}
return (
<>
{actionable}
<Menu
open={isMenuOpen && !!menuAnchorRef.current}
onClose={() => setIsMenuOpen(false)}
anchorEl={menuAnchorRef.current}
>
{children}
</Menu>
</>
);
};
答案1
得分: 1
以下是代码部分的中文翻译:
type ConditionalActionableProps<T extends ActionableType> = {
actionableType: T;
actionableProps: ActionableProps<T>;
};
这意味着:
type ConditionalActionableProps = {
actionableType: "button" | "iconButton";
actionableProps: ActionableProps<"button" | "iconButton">;
};
因为在定义以下内容时,类型仍然是泛型的,还没有具体化:
const ConditionalComponent = <T extends ActionableType = "iconButton">({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps<T>) => { ...
因此,基于actionableType
进行的区分会失效。以下内容可以实现所期望的类型缩小:
type ConditionalActionableProps<T extends ActionableType> =
T extends 'button'
? {
actionableType: T;
actionableProps: ActionableProps<T>
}
: T extends 'iconButton'
? {
actionableType: T;
actionableProps: ActionableProps<T>
} : never
但这种方式较为冗长。
英文:
The type
type ConditionalActionableProps<T extends ActionableType> = {
actionableType: T;
actionableProps: ActionableProps<T>;
};
means
type ConditionalActionableProps = {
actionableType: "button" | "iconButton";
actionableProps: ActionableProps<"button" | "iconButton">;
};
because the type is still generic and not yet narrowed when defining
const ConditionalComponent = <T extends ActionableType = "iconButton">({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps<T>) => { ...
so discrimination based on actionableType breaks. This does narrow the type as desired:
type ConditionalActionableProps<T extends ActionableType> =
T extends 'button'
? {
actionableType: T;
actionableProps: ActionableProps<T>
}
: T extends 'iconButton'
? {
actionableType: T;
actionableProps: ActionableProps<T>
} : never
but it is verbose
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论