React props 的区分性联合类型与条件类型

huangapple go评论69阅读模式
英文:

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&#39;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 &quot;react&quot;;
import { Button, ButtonProps, IconButton, Menu } from &quot;@neo4j-ndl/react&quot;;
import type { IconButtonProps, ContextMenuProps } from &quot;@neo4j-ndl/react&quot;;
type ActionableType = &quot;button&quot; | &quot;iconButton&quot;;
type ActionableProps&lt;T extends ActionableType&gt; = {
&quot;data-testid&quot;: string;
} &amp; T extends &quot;button&quot; // narrowing happens here &#128072;
? Omit&lt;ButtonProps, &quot;onClick&quot; | &quot;ref&quot; | &quot;children&quot;&gt; &amp; {
zzButtonText: ButtonProps[&quot;children&quot;];
}
: Omit&lt;IconButtonProps, &quot;onClick&quot; | &quot;ref&quot; | &quot;children&quot;&gt; &amp; {
zzIcon: IconButtonProps[&quot;children&quot;];
};
type ConditionalActionableProps&lt;T extends ActionableType&gt; = {
actionableType: T;
actionableProps: ActionableProps&lt;T&gt;;
};
type ActionableWithMenuProps&lt;T extends ActionableType&gt; =
ConditionalActionableProps&lt;T&gt; &amp; {
menuProps: Omit&lt;
ContextMenuProps,
&quot;open&quot; | &quot;onClose&quot; | &quot;anchorEl&quot; | &quot;ref&quot; | &quot;children&quot;
&gt;;
children: ContextMenuProps[&quot;children&quot;];
};
const ConditionalComponent = &lt;T extends ActionableType = &quot;iconButton&quot;&gt;({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps&lt;T&gt;) =&gt; {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuAnchorRef = useRef(null);
let actionable: JSX.Element;
if (actionableType === &quot;iconButton&quot;) {
const { zzIcon, ...rest } = actionableProps; // &#128072; why actionableProps is not narrowed
actionable = &lt;IconButton {...rest}&gt;{zzIcon}&lt;/IconButton&gt;;
} else {
const { zzButtonText, ...rest } = actionableProps;
actionable = &lt;Button {...rest}&gt;{zzButtonText}&lt;/Button&gt;;
}
return (
&lt;&gt;
{actionable}
&lt;Menu
open={isMenuOpen &amp;&amp; !!menuAnchorRef.current}
onClose={() =&gt; setIsMenuOpen(false)}
anchorEl={menuAnchorRef.current}
&gt;
{children}
&lt;/Menu&gt;
&lt;/&gt;
);
};

答案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&lt;T extends ActionableType&gt; = {
actionableType: T;
actionableProps: ActionableProps&lt;T&gt;;
};

means

type ConditionalActionableProps = {
actionableType: &quot;button&quot; | &quot;iconButton&quot;;
actionableProps: ActionableProps&lt;&quot;button&quot; | &quot;iconButton&quot;&gt;;
}; 

because the type is still generic and not yet narrowed when defining

const ConditionalComponent = &lt;T extends ActionableType = &quot;iconButton&quot;&gt;({
actionableType,
actionableProps,
menuProps,
children,
}: ActionableWithMenuProps&lt;T&gt;) =&gt; { ...

so discrimination based on actionableType breaks. This does narrow the type as desired:

type ConditionalActionableProps&lt;T extends ActionableType&gt; =
T extends &#39;button&#39;
? {
actionableType: T;
actionableProps: ActionableProps&lt;T&gt;
}
: T extends &#39;iconButton&#39;
? {
actionableType: T;
actionableProps: ActionableProps&lt;T&gt;
} : never

but it is verbose

huangapple
  • 本文由 发表于 2023年2月23日 21:59:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75545795.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定