简体   繁体   English

从实用程序类型中解构道具(提取和排除 - TypeScript 和 React)

[英]Destructuring props from Utility Types (Extract and Exclude - TypeScript and React)

I'm facing a problem with Typescript and React.我遇到了 Typescript 和 React 的问题。

Let's supposed I have the following Union Type:假设我有以下联合类型:

type Fruits = 'apple' | 'peach' | 'banana';

And I use it like this:我像这样使用它:

type FoodBaseProps = {
  fruits: Fruits;
  weight: number
  price: number
} 

Now, imagine that I have a boolean prop called needToPeel that should only be informed if the fruit banana is passed;现在,假设我有一个名为needToPeel的 boolean 道具,只有在水果banana通过时才会被告知;

To solve that I did the following:为了解决这个问题,我做了以下事情:

export type NeedToPeelFruitsProps = FoodBaseProps & {
  fruits: Extract<Fruits, 'banana'>;
  needToPeel?: boolean;
};

And for the other fruits that do not need to be peeled I have:对于其他不需要去皮的水果,我有:

export type DefaultFruitsProps = FoodBaseProps & {
  fruits: Exclude<Fruits, 'banana'>;
};

And then I have:然后我有:

type FoodProps = NeedToPeelFruitsProps | DefaultFruitsProps;

Now, if I do some assertions based on this logic I'll have this (working as expected):现在,如果我根据这个逻辑做一些断言,我会得到这个(按预期工作):

const testFn = (food: FoodProps) => {
  switch (food.fruits) {
    case 'banana': {
      console.log(food.needToPeel); // No error
      break;
    }

    case 'apple': {
      console.log(food.needToPeel); // TS2339: Property 'needToPeel' does not exist on type 'DefaultFruitsProps'.
      break;
    }

    default:
  }
};

However, if I try to access this needToPeel prop in my React component, I'll have some problems:但是,如果我尝试在我的 React 组件中访问这个needToPeel ,我会遇到一些问题:

const Food = ({
  fruits,
  weight,
  price,
  needToPeel, // TS2339: Property 'needToPeel' does not exist on type 'FoodProps'.
}: FoodProps) => {
  return (
    <FoodBase
      fruits={fruits}
      weight={weight}
      price={price}
      needToPeel={needToPeel} // 2 errors below
    />
  )
}

1 - TS2769: No overload matches this call. Overload 1 of 2, '(props: Omit<Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof HTMLAttributes<...>> & {...; } & [...] 1 - TS2769: No overload matches this call. Overload 1 of 2, '(props: Omit<Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof HTMLAttributes<...>> & {...; } & [...] TS2769: No overload matches this call. Overload 1 of 2, '(props: Omit<Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof HTMLAttributes<...>> & {...; } & [...] ; TS2769: No overload matches this call. Overload 1 of 2, '(props: Omit<Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof HTMLAttributes<...>> & {...; } & [...] ;

2 - TS2339: Property 'needToPeel' does not exist on type 'FoodProps'. 2 - TS2339: Property 'needToPeel' does not exist on type 'FoodProps'.

I think the problem is because union types only consider the props in common.我认为问题在于联合类型只考虑共同的道具。 So, needToPeel prop will not be accessible since it's not shared among the types.因此, needToPeel将无法访问,因为它不在类型之间共享。

I was wondering if there's a way to be able to access the needToPeel in my React component.我想知道是否有办法在我的 React 组件中访问needToPeel

I think the problem is because union types only consider the props in common.我认为问题在于联合类型只考虑共同的道具。

Indeed, when a variable is typed as a union, the only safe thing we know is that it has the common properties .事实上,当一个变量被键入为一个联合时, 我们知道的唯一安全的事情是它具有公共属性

TypeScript will only allow an operation if it is valid for every member of the union. TypeScript 只有在对联合体的每个成员都有效的情况下才允许操作。 For example, if you have the union string | number例如,如果您有联合string | number string | number , you can't use methods that are only available on string string | number ,您不能使用仅在string上可用的方法

The only safe way to access the specific properties, is to first narrow the type, eg like in your "assertion" example.访问特定属性的唯一安全方法是首先缩小类型,例如在您的“断言”示例中。

The solution is to narrow the union with code, the same as you would in JavaScript without type annotations.解决方案是使用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。 Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.TypeScript可以根据代码结构推断出更具体的值类型时,就会发生缩小。


if I try to access this needToPeel prop in my React component, I'll have some problems如果我尝试在我的 React 组件中访问这个needToPeel道具,我会遇到一些问题

That is because if we do the typical props destructuring of React functional components from the function argument definition (like in the question example), this destructuring occurs too early.这是因为如果我们从 function 参数定义中对 React 功能组件进行典型的 props 解构(如问题示例中所示),这种解构发生得太早了。 If we want to destructure and get the specific needToPeel property, it must occur after type narrowing.如果我们想解构得到具体的needToPeel属性,它必须发生类型缩小之后。

I would consider such situation a legit reason to deviate from this standard practice: as implied, there is no hard technical requirement to destructure the props from the function argument definition;我认为这种情况是偏离这个标准实践的正当理由:正如暗示的那样,从 function 参数定义中解构道具没有硬性技术要求; it is rather a common practice for React functional components;这是 React 功能组件的常见做法; in React class-based components, we have to use this.props anyway.在 React 基于类的组件中,无论如何我们都必须使用this.props

One of the very first examples of component with props in React documentation also does not use destructuring: React 文档中最先使用 props 的组件示例之一也没有使用解构:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

So in your case, you can very well postpone the destructuring to after type narrowing (or even not destructure at all):因此,在您的情况下,您可以很好地将解构推迟到类型缩小之后(甚至根本不进行解构):

const Food = (props: FoodProps) => {
  // Narrowing
  if (props.fruits === "banana") {
    // Now TS knows that props is a NeedToPeelFruitsProps
    return (
      <FoodBase
        fruits={props.fruits}
        weight={props.weight}
        price={props.price}
        needToPeel={props.needToPeel} // Okay
      />
    );
  }
  // Depending on the signature of <FoodBase>,
  // you could even blindly pass the props,
  // which may or may not have the needToPeel property
  return (
    <FoodBase
      {...props}
    />
  )
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM