简体   繁体   English

当预期的返回值数量错误时,TypeScript 不会抛出错误

[英]TypeScript doesn't throw an error when expected number of returned values is wrong

For the sake of time and simplicity, the code examples in this question are simplified.为了时间和简单起见,这个问题中的代码示例被简化了。

I have a function that takes in an array of objects and for each object in that array it creates a variation of a product.我有一个 function 接收一个对象数组,并且对于该数组中的每个 object 它创建一个产品的变体。 Then, it returns all of the variations as an array of those variation objects.然后,它将所有变体作为这些变体对象的数组返回。

const generateProducts = (productMockData: object[]) => {
    const products = productMockData.map((mockData) => {
         // create a variation of the product based on mock data
         const productVariation = ...
         
         return productVariation;
    })

    return [...products]
}

Here's an example of how this function is used:这是如何使用此 function 的示例:

const [productVariation1, productVariation2] = generateProducts([{color: 'red', price: 20}, {color: 'green', price: 100}])

In the example above, we passed in an array of two objects to the generateProducts function, so the function returns an array of exactly two variation objects.在上面的示例中,我们将一个包含两个对象的数组传递给generateProducts function,因此 function 返回一个恰好包含两个变体对象的数组。

The issue is that if you accidentally expect an array of three objects to be returned from the function, instead of two, TypeScript doesn't throw an error.问题是,如果您不小心期望从 function 返回一个包含三个对象的数组,而不是两个,则 TypeScript 不会引发错误。

const [productVariation1, productVariation2, productVariation3] = generateProducts([{color: 'red', price: 20}, {color: 'green', price: 100}])

In this case, we expect to have three variations of a product, but there are only two, since we only passed two to the generateProducts function.在这种情况下,我们希望产品有三个变体,但只有两个,因为我们只将两个传递给 generateProducts function。 Still, TypeScript doesn't throw an error.尽管如此, TypeScript 不会引发错误。

Is there a way to get better TypeScript support for this use case?有没有办法为这个用例获得更好的 TypeScript 支持?

The reason that your debuger is not trowing an error is that ists not an error... Yourself have to decide if this "interaction" is valid or not.你的调试器没有抛出错误的原因是它不是错误......你自己必须决定这个“交互”是否有效。

Your generateProducts function looks great but if you want to validate if the program is trying to have 3 variation based on 2 model object (the parameters given to the function) I think you have to fundmental choices:您的 generateProducts function 看起来不错,但如果您想验证程序是否尝试基于 2 model object (给函数的参数选择)有 3 个变体:

1- Create a class that implements you generateProducts function and a property that will hold your array with null variables => [null,null,null] and then. 1- Create a class that implements you generateProducts function and a property that will hold your array with null variables => [null,null,null] and then.

 export new ProductionHelper { public array: Array < object | ProductsVariation | null > = []; public generateProducts(productMockData: Array < ProductsVariation > ) { if (this.array.lentgth === productMockData.length) { let i = 0; productMockData.map((mockData) => { // create a variation of the product based on mock data ** createdObj ** this.array[i] = createdObj; }); return this.array; } else { throw [ return ] new Error("Number of variation is not equal to length of array"); } return; } }

Second solution is to had your returned array has second parameter for your function =>第二种解决方案是让您返回的数组具有 function 的第二个参数 =>

 function generateProducts(productMockData: object[], productVar: object[])...

The as keyword can be used to cast a value to a more specific version of the expected type. as关键字可用于将值转换为预期类型的更具体版本。 So, you could use, for example所以,你可以使用,例如

// No Error
const products = generateProducts([{color: 'red', price: 20}, {color: 'green', price: 100}]);
const [productVariation1, productVariation2] = products as [object, object];
// Error: Tuple type '[object, object]' of length '2' has no element at index '2'.
const products = generateProducts([{color: 'red', price: 20}, {color: 'green', price: 100}]);
const [productVariation1, productVariation2, productVariation3] = products as [object, object];

Obviously, a more specific type can be used than object, for example显然,可以使用比 object 更具体的类型,例如

interface Product {
  color: string;
  price: number;
}

In what follows I will ignore the implementation of generateProducts() and assume it has the following call signature:在下文中,我将忽略generateProducts()的实现,并假设它具有以下调用签名:

declare const generateProducts: (productMockData: object[]) => object[];

Unless you enable the --noUncheckedIndexedAccess compiler option , the compiler will assume that you will always get a defined element if you read from an array type via a numeric index.除非您启用--noUncheckedIndexedAccess编译器选项,否则编译器将假定如果您通过数字索引从数组类型中读取,您将始终获得定义的元素。 It ignores the possibility that there is no element at the index (eg, the index is negative, or not an integer, or past the end of the array, etc.) If you read the third element of an array that happens to have only two elements, you will get undefined at runtime but the compiler will completely miss this.它忽略了索引处没有元素的可能性(例如,索引为负数,或不是 integer,或超出数组末尾等)如果您读取的数组的第三个元素恰好只有两个元素,您将在运行时得到undefined ,但编译器将完全错过这一点。

One thing you could do here is enable --noUncheckedIndexedAccess , which would address the situation by forcing you to deal with possible undefined values:可以在这里做的一件事是启用--noUncheckedIndexedAccess ,这将通过强制您处理可能的undefined值来解决这种情况:

const [productVariation1, productVariation2, productVariation3] =
  generateProducts([{ color: 'red', price: 20 }, { color: 'green', price: 100 }])

if ("color" in productVariation3) { } // <-- error, Object is possibly 'undefined'
if (productVariation3 && "color" in productVariation3) { } // <-- okay

but this is often more trouble than it's worth;但这往往比它的价值更麻烦; the same check will have to happen for productVariation1 , which you know exists:您知道存在的productVariation1必须进行相同的检查:

if ("color" in productVariation1) { } // error, grr
if (productVariation1 && "color" in productVariation1) { } // forcing me to check

which will tempt you to skip the check by using a non-null assertion :这将诱使您使用非空断言跳过检查:

if ("color" in productVariation1!) { } // <-- I assert this is fine

and if you get used to using such assertions, then you might overuse them, which leads you back to the same problem as before with productVariation3 , with some extra steps:如果你习惯了使用这样的断言,那么你可能会过度使用它们,这会导致你回到与之前使用productVariation3相同的问题,需要一些额外的步骤:

if ("color" in productVariation3!) { } // <-- I assert this is fine, wait oops

And as such, the --noUncheckedIndexedAccess compiler option is not included in the --strict suite of compiler features .因此, --noUncheckedIndexedAccess编译器选项不包含在--strict编译器功能套件中 If you want to use it and it helps you, great.如果您想使用它并且它可以帮助您,那就太好了。 But otherwise, you might want to take a different approach.但除此之外,您可能希望采用不同的方法。


A different way of looking at things is that your generateProducts() call signature currently returns an array of unknown length.看待事物的另一种方式是您的generateProducts()调用签名当前返回一个未知长度的数组。 But if you know that the returned array will always have the same length as the input array, then you can try to take advantage of tuple types ;但是如果您知道返回的数组将始终与输入数组具有相同的长度,那么您可以尝试利用元组类型 make generateProducts() a generic function which tries to figure out the exact length of its input (via variadic tuple type notation), and then have it return a mapped tuple type which of the same length (but possibly a different element type).使generateProducts()成为一个通用的 function 试图找出其输入的确切length (通过可变元组类型表示法),然后让它返回一个映射的元组类型,它具有相同的长度(但可能是不同的元素类型)。 For example:例如:

declare const generateProducts: <T extends object[]>(
  productMockData: [...T]) => { [I in keyof T]: object };

And now when we call it with an array of known length, you can see that the compiler keeps that length in the output type:现在,当我们使用已知长度的数组调用它时,您可以看到编译器将该长度保存在 output 类型中:

const ret =
  generateProducts([{ color: 'red', price: 20 }, { color: 'green', price: 100 }]);
// const ret: [object, object]

And this immediately gives you the error you want and only where you want it:这会立即为您提供您想要的错误,并且只在您想要的地方:

const [productVariation1, productVariation2, productVariation3] = // error!
  // --------------------------------------> ~~~~~~~~~~~~~~~~~
  // Tuple type '[object, object]' of length '2' has no element at index '2'
  generateProducts([{ color: 'red', price: 20 }, { color: 'green', price: 100 }])

The compiler knows that productVariation1 and productVariation2 are defined, while productVariation3 is not:编译器知道productVariation1productVariation2已定义,而productVariation3未定义:

// const productVariation1: object
// const productVariation2: object
// const productVariation3: undefined

This is pretty much exactly what you're looking for.这几乎正是您正在寻找的。

The big caveat here is that the compiler does not track the exact length of all arrays.这里最大的警告是编译器不会跟踪所有 arrays 的确切长度。 If you pass an array literal directly into generateProducts() , the compiler will pay attention to the length, but in most cases an array literal will be widened to an array and not have its "tuple-ness" maintained:如果将数组文字直接传递给generateProducts() ,编译器会注意长度,但在大多数情况下,数组文字将被扩展为数组,并且不会维护其“元组”:

const objects = [{ color: "red" }, { color: "green" }];
// const objects: { color: string; }[]

The above objects is an arbitrary array of {color: string} elements, and the compiler does not know how many such elements there are.上述objects{color: string}元素的任意数组,编译器不知道有多少这样的元素。 If you pass that to generateProducts() , then you get the old behavior again:如果您将传递给generateProducts() ,那么您将再次获得旧行为:

const oops = generateProducts(objects);
// const oops: object[]

The compiler has no idea how many elements are in oops , and so if you destructure that into a bunch of variables, each one will either be of type object or of type object | undefined编译器不知道oops中有多少元素,因此如果将其解构为一堆变量,每个变量要么是object类型,要么是object | undefined类型。 object | undefined depending on the status of the --noUncheckedIndexedAccess compiler option. object | undefined取决于--noUncheckedIndexedAccess编译器选项的状态。

This caveat probably shouldn't come as much of surprise;这个警告可能不应该让人感到意外。 the situations in which a compiler can keep track of the exact number of elements in an array are pretty limited by necessity.编译器可以跟踪数组中元素的确切数量的情况非常有限。 Arrays are quite often used to hold an arbitrary and variable amount of data, and quite often are supplied by or modified by code that the compiler cannot access or fully analyze. Arrays 经常用于保存任意和可变数量的数据,并且经常由编译器无法访问或完全分析的代码提供或修改。 So for the general case you're probably stuck with having the compiler be lax and let you read elements off the end of the array or strict and scold you into null-checking elements you know to be defined.因此,对于一般情况,您可能会坚持让编译器松懈,让您从数组末尾读取元素,或者严格并责骂您进入您知道要定义的空检查元素。 But in the specific scenario like your example, where you write TypeScript code by calling a function and directly pass it an array literal of a known number of elements, you can get some stronger typing and nicer behavior.但是在像您的示例这样的特定场景中,您通过调用 function 编写 TypeScript 代码并直接将其传递给已知元素数量的数组文字,您可以获得一些更强的输入和更好的行为。


Playground link to code Playground 代码链接

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

相关问题 “noUnusedLocals”在使用 typescript 编译时不会引发错误 - "noUnusedLocals" doesn't throw error on compiling with typescript 当它可以抛出错误 Typescript 时,用期望值解构一个 Promise - Deconstruct a Promise with the expected value when it can throw an Error Typescript 如果 function 返回错误的接口,Typescript 不会抛出错误 - Typescript does't throw an error if a function returns the wrong interface 为什么在隐式类型的返回对象上返回无效属性时,TypeScript 不会抛出错误? - Why doesn't TypeScript throw an error when returning invalid properties on implicitly typed return object? Typescript 在传递错误类型的 prop 时不会抛出错误 - Typescript does not throw an error when wrong type of prop is passed Typescript 不会在 .d.ts 文件中抛出错误 - Typescript doesn't throw error in .d.ts files 为什么下面的 TypeScript 程序不会抛出类型错误? - Why doesn't the following TypeScript program throw a type error? TypeScript 断言在属性为记录时不起作用<number,number></number,number> - TypeScript Assertion Doesn't work when the property is Record<number,number> 当我为对象分配数字时,为什么TypeScript不会引发错误? - Why doesn't TypeScript raise an error when I assign a number to an Object? 为什么 Typescript 不检查数值枚举? - Why does Typescript doesn't check values number-enums?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM