任一类型包含不同类型

2023-12-01

[原始帖子已编辑为简化示例]

我想创建一个组件,该组件将采用一对值(这两个值都基于相同类型 ->value, setValue),并且基于它,将运行取决于这些类型的函数。为了充分说明情况:

AnimalAndCatTypes.ts:

export interface Animal {
  type: string,
  age: number
}

export interface Cat extends Animal {
  typeOfCat: string,
}

ChangeSpecies.tsx:

import React from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";

interface AnimalVersion {
  value: Animal;
  setValue: React.Dispatch<React.SetStateAction<Animal>>;
}

interface CatVersion {
  value: Cat;
  setValue: React.Dispatch<React.SetStateAction<Cat>>;
}

type Props = AnimalVersion | CatVersion;

const ChangeSpecies = (props: Props): JSX.Element => {
  const { value, setValue } = props;

  const handleChange = (data: string) => {
    setValue({ ...value, type: data });
  };

  return (
    <form>
      <label>
        <input
          type="text"
          value={value.type}
          onChange={(data) => handleChange(data.target.value)}
        />
      </label>
    </form>
  );
};

export default ChangeSpecies;

示例组件.tsx:

import React, { useState } from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";
import ChangeSpecies from "./ChangeSpecies";

const ExampleComponent = (): JSX.Element => {
  const [animal, setAnimal] = useState<Animal>({
    type: "dog",
    age: 2
  });
  const [cat, setCat] = useState<Cat>({
    type: "cat",
    age: 1,
    typeOfCat: "persian"
  });

  return (
    <div>
      <ChangeSpecies value={animal} setValue={setAnimal} />
      <ChangeSpecies value={cat} setValue={setCat} />
    </div>
  );
};

export default ExampleComponent;

问题在于setValue打字稿说不可能Animal用于React.Dispatch<React.SetStateAction<Cat>>因为失踪typeOfCat.

如何创建一个 ts 知道的类型PropsCat or Animal创建组件时通过检查value and setValue来自一对。稍后,根据该对类型,将采取适当的setValue链接到的类型value类型并且不能与另一个混合?

[编辑中添加]

复制链接:https://codesandbox.io/s/zealous-mirzakhani-1x4kz?file=/src/App.js


主要问题在于这个联盟:

type Props = AnimalVersion | CatVersion

Because Props是一个联盟,setValue也是一个联盟React.Dispatch<React.SetStateAction<Animal>> | React.Dispatch<React.SetStateAction<Cat>>.

根据docs:

同样,逆变位置中同一类型变量的多个候选会导致推断交集类型:

这意味着,如果你想调用这样的函数,联合中所有函数的参数将合并(相交),这就是为什么setValue期望React.SetStateAction<Animal> & React.SetStateAction<Cat>.

请看简单的例子:

declare var foo: (val: { name: string }) => void
declare var bar: (val: { surname: number }) => void

declare var union:typeof foo | typeof bar

// expects { name: string;} & { surname: number; }
union()

大多数时候,人们都在接受never, 因为这:

declare var foo: (val:  string) => void
declare var bar: (val:  number ) => void

declare var union:typeof foo | typeof bar

// expects never
union()

string & number-> 产生never,因为这种类型是不可表示的。

在你的情况下,setValue期望Cat, 因为Cat是一个子类型Animal。超类型的交集Animal和亚型Cat- 给你Cat类型。你有一个错误,因为setValue期望Cat然而props.value可能是一个Animal, 无需额外 typeOfCat type.

现在,当我们知道为什么会出现交叉点时,我们就可以继续了。

为了修复它,您可以为Animal|Cat:

import React from "react";

export interface Animal {
  type: string,
  age: number
}

export interface Cat extends Animal {
  typeOfCat: string,
}


interface AnimalVersion {
  value: Animal;
  setValue: React.Dispatch<React.SetStateAction<Animal>>;
}

interface CatVersion {
  value: Cat;
  setValue: React.Dispatch<React.SetStateAction<Cat>>;
}


type Props<T> = {
  value: T,
  setValue: React.Dispatch<React.SetStateAction<T>>;
}


const ChangeSpecies = <T extends Animal | Cat,>(props: Props<T>): JSX.Element => {
  const { value, setValue } = props;

  const handleChange = (data: string) => {
    setValue({ ...value, type: data });
  };

  return (
    <form>
      <label>
        <input
          type="text"
          value={value.type}
          onChange={(data) => handleChange(data.target.value)}
        />
      </label>
    </form>
  );
};

const jsx = <ChangeSpecies value={{ type: 'animal', age: 42 }} setValue={(elem /** elem is animal */) => {

}} />


const jsx2 = <ChangeSpecies value={{ type: 'animal', age: 42, typeOfCat: 'cat' }} setValue={(elem /**elem is infered as a cat */) => {

}} />

// error
const expectedError = <ChangeSpecies value={{ type: 'animal', typeOfCat: 'cat' }} setValue={(elem ) => {

}} />

操场

现在,TS 意识到value具有有效类型setValue。因为,这两种类型都有type属性,这条线setValue({ ...value, type: data });已验证

但是,您应该意识到此解决方案的缺点:


type Props<T> = {
  value: T,
  setValue: React.Dispatch<React.SetStateAction<T>>;
}

type Result = Props<Animal | Cat>

const invalid: Result = {
  value: { type: 'animal', age: 42 },
  setValue: (elem /**React.SetStateAction<Animal | Cat> */) => {

  },
}

TS还是这么认为elem是一个联盟。

这意味着如果您在组件构建期间提供显式的联合泛型:


const jsx = <ChangeSpecies<Animal|Cat> value={{ type: 'animal', age: 42 }} setValue={(elem) => {

}} />

TS 会让你做到这一点。


第二个选择

更安全的解决方案是使用分配条件类型:

type Props<T> = T extends any ? {
  value: T,
  setValue: React.Dispatch<React.SetStateAction<T>>;
} : never

type Result = Props<Animal | Cat>

事实上,这个类型就等于你对 Props union 的表示。它只是以更通用的方式编写。

如果您想坚持使用此解决方案,则应该创建自定义打字保护。这意味着它将影响您的运行时间。同时,可以为值编写类型保护,例如isString or isNumer,很难为函数编写类型保护。因为你只能通过引用或arity.length来比较函数。因此,我认为,这是不值得的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

任一类型包含不同类型 的相关文章

随机推荐

  • 合并两个都有联结表的 SQLite 数据库

    我有两个 SQLite 数据库 它们都有连接表来描述一对多关系 现在 这两个数据库需要通过某种导入 导出机制合并为一个数据库 并仍然保留关系 我试图转储DB2 with dump然后将其加载回DB1 with read 但总是得到PRIMA
  • 如何制作克隆或扩展模式

    我有多个显示器 虚拟 真实 默认情况下我想从我的程序将其置于克隆 双模式 有人可以建议我如何实现这一目标吗 我对这个世界很陌生 对于这个基本问题感到抱歉 即使任何文档或任何链接都将非常值得赞赏 萨博吉特 Extend SetDisplayC
  • MSP430G2553 定时器间隔 [关闭]

    Closed 这个问题需要细节或清晰度 目前不接受答案 在阅读了大约五遍文字并进行谷歌搜索后 我决定寻求帮助 我目前正在使用 Timer A 中断以 1 秒 10 秒 1 分钟的间隔一次打开 关闭两个 LED 默认程序每秒都会打开 关闭 L
  • 我可以阻止特定数据成员被反序列化吗?

    我有这样的数据合同 DataContract class MyDC DataMember public string DM1 DataMember public string DM2 DataMember public string DM3
  • QTP 中的 RO 属性和 TO 属性有什么区别?

    在QTP中每个测试对象都支持以下方法 GetRoProperty GetToProperty SetToProperty GetToProperties 和有什么区别RO 财产和TO property 为什么我只能设置一个TO 财产而不是R
  • 如果 id 匹配,则从 mysql 选择行

    我想从 mysql 中选择与特定 id 匹配的行 我想获取ID是否匹配的结果 如果数据库中不存在该ID 则不应该执行任何操作 我这样运行 q SELECT FROM entries where id 1 result mysql query
  • Visual Studio 2017 - Git 因致命错误而失败

    我使用的是 Visual Studio 2017 Community Edition CE 并且已登录我的 Microsoft 帐户并连接到 VSTS 我可以看到我的所有项目和存储库 但是当我尝试拉 取 推送任何更改时 我收到以下错误 Er
  • 设置包括 Wamp 上 PEAR 的路径

    安装 PEAR 并按照上的说明进行操作http www phpunit de manual current en installation html pear config set auto discover 1 pear install
  • 从资源加载dll库到当前域(在主exe文件中嵌入dll)

    我尝试使用以下代码在运行时加载 dll 库 这样我就不必向用户提供大量 dll 文件以及主可执行文件 我已将所有 dll 文件作为嵌入式资源包含在内 并且在参考部分中我已包含它们并设置了复制本地属性为 false 但这里的问题是 1 所有d
  • 在 Firebase 中检索嵌套数据 - Android

    我刚刚进入 firebase 和 android 的世界 我按照在线教程设置和获取非嵌套数据并将其显示在列表视图中 如下所示 数据之前 mListView ListView findViewById R id ListView final
  • UIView 背景颜色影响 iOS 5 中的触摸

    我有一个在 iOS 4 中运行的具有子类触摸响应的自定义视图 在 iOS 5 上 当沿着视图的底部边缘触摸时 这些触摸根本不会响应 if视图的背景颜色设置为clearColor 我一直无法追踪到这一点 但有谁知道 iOS 5 是否改变了视图
  • 如何让 perf stat 支持 KVM 中的“分支”、“分支未命中”等硬件事件

    我想通过 分支未命中 硬件事件来评估一个进程的性能 但是当我使用 perf stat 获取 分支未命中 数据时 它总是返回 0 因为我的操作系统位于 KVM 中 因为我拿一台真机来做测试比较麻烦 所以我想知道当我在 KVM 中时 有什么方法
  • 具有多个模板的 ASP Repeater 控件

    如何拥有具有多个模板的转发器控件 其中选择的模板基于项目的类型 这就是我目前所拥有的 我的复读班 ToolboxData lt 0 LifestreamRepeater runat server gt public class Lifest
  • 变异观察者产生无限循环

    我正在使用 jQuery 的突变观察器编写一个函数来注册对 DOM 的更改 特别是在添加新节点时 以便我可以更改其内容 SELeCTOR GOOD click function var targetNode this find conten
  • 表达 sendfile 和重定向 url

    我有一堆中间件 一开始app use我测试该过程是否受到胁迫 如果是 我希望它只发送静态 index html文件并将用户的浏览器重定向到 req url 例如 app set port PORT etc app use function
  • 设置 R_LIBS 并避免“您想使用个人库吗?”

    我的个人库在 Renviron中设置为R LIBS R lib 当我从 rstudio 安装软件包时 这非常有效 当我尝试从普通 R 控制台会话安装新软件包时 它总是询问我 Would you like to use a personal
  • 可以使用缓冲读取来计算 MD5(或其他)哈希值吗?

    我需要计算相当大的文件 千兆字节 的校验和 这可以使用以下方法来完成 private byte calcHash string file System Security Cryptography HashAlgorithm ha Syste
  • 向非引用类型添加生命周期约束

    我试图弄清楚如何应用 Rust 生命周期来向 Erlang NIF 模块添加一些编译时强制 NIF 模块是通常用 C 编写的提供扩展的共享库 用 C 语言编写的回调的简化原型如下所示 Handle my nif function Heap
  • 如何在 Zend Framework 2 中获取控制器名称、操作名称

    我在 Zend Framework 2 中有一个默认模块 namespace Application Controller use Zend Mvc Controller AbstractActionController use Zend
  • 任一类型包含不同类型

    原始帖子已编辑为简化示例 我想创建一个组件 该组件将采用一对值 这两个值都基于相同类型 gt value setValue 并且基于它 将运行取决于这些类型的函数 为了充分说明情况 AnimalAndCatTypes ts export i