为什么Array.prototype.includes(searchElement)的参数需要与数组元素相同的类型?

时间:2018-10-28 16:47:12

标签: javascript typescript

老实说,我不知道我的设置是否有问题或这是否是打字稿功能。在以下示例中:

type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];

function checkKey(e: KeyboardEvent) { 
    if (exampleArr.includes(e.key)) { // <-- here
        // ...
    } 
}

打字稿编译器抱怨Argument of type 'string' is not assignable to parameter of type 'AllowedChars'.,但是我应该分配给哪里? Array.prototype.includes返回一个布尔值(我没有存储)。我可以通过类型断言来使错误保持沉默,如下所示:

if (exampleArr.includes(e.key as AllowedChars)) {}

但是这是正确的,我正在接受用户输入,可能是任何东西。我不明白为什么检查数组中是否存在 的函数(Array.prototype.includes())应该了解预期的输入类型。

我的tsconfig.json(打字稿v3.1.3):

 {
    "compilerOptions": {
      "target": "esnext",
      "moduleResolution": "node",
      "allowJs": true,
      "noEmit": true,
      "strict": true,
      "isolatedModules": true,
      "esModuleInterop": true,
      "jsx": "preserve",
    },
    "include": [
      "src"
    ],
    "exclude": [
      "node_modules",
      "**/__tests__/**"
    ]
  }

任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:3)

是的,从技术上讲,应该允许import React from 'react'; import { StyleSheet, ScrollView, Image, Dimensions } from 'react-native' import { ImagePicker, Permissions } from 'expo' import { Icon } from 'native-base' import Amplify from '@aws-amplify/core' import Storage from '@aws-amplify/storage' import config from './aws-exports' class App extends React.Component { state = { image: null, } // fetch a single image from user's device and save it to S3 useLibraryHandler = async () => { await this.askPermissionsAsync() let result = await ImagePicker.launchImageLibraryAsync( { allowsEditing: false, //aspect: [4, 3], } ) console.log(result); if (!result.cancelled) { this.setState({ image: result.uri }) this.uploadImage(this.state.image) } } // add a single image to S3 uploadImage = async uri => { const response = await fetch(uri) const blob = await response.blob() // format the data for images const folder = 'images' const fileName = 'flower.jpeg' await Storage.put(folder + '/' + fileName, blob, { contentType: 'image/jpeg', level: 'public' }).then(data => console.log(data)) .catch(err => console.log(err)) } render() { let { image } = this.state let {height, width} = Dimensions.get('window') return ( <ScrollView style={{flex: 1}} contentContainerStyle={styles.container}> <Icon name='md-add-circle' style={styles.buttonStyle} onPress={this.useLibraryHandler} /> {/* true && expression always evaluates to expression, and false && expression always evaluates to false */} {image && <Image source={{ uri: image }} style={{ width: width, height: height/2 }} /> } </ScrollView> ); } } 中的searchElement参数为Array<T>.includes()的超类型,但是standard TypeScript library declaration假定它只是{{ 1}}。在大多数情况下,这是一个很好的假设,因为您通常不需要像@GustavoLopes所提到的那样完全比较不相关的类型。但是您的类型不是完全无关的吗?

有不同的处理方法。您所做的断言可能是最不正确的断言,因为您断言TT,尽管可能并非如此。它“完成了工作”,但是您对此感到不安是正确的。


另一种方法是通过declaration merging在本地重写标准库以接受超类型,这有点复杂并且使用conditional types

string

然后您的原始代码就可以使用:

AllowedChars

同时仍然阻止比较完全不相关的类型:

// remove "declare global" if you are writing your code in global scope to begin with
declare global {
  interface Array<T> {
    includes<U extends (T extends U ? unknown : never)>(searchElement: U, fromIndex?: number): boolean;
  }
}

但是处理此问题的最简单但仍然正确的方法是将if (exampleArr.includes(e.key)) {} // okay // call to includes inspects as // (method) Array<AllowedChars>.includes<string>(searchElement: string, fromIndex?: number | undefined): boolean (+1 overload) 的类型扩展为if (exampleArr.includes(123)) {} // error // Argument of type '123' is not assignable to parameter of type 'AllowedChars'.

exampleArr

或更简洁地说:

string[]

通向const stringArr: string[] = exampleArr; // no assertion if (stringArr.includes(e.key)) {} // okay 只是“有点”正确,因为为方便起见,TypeScript不安全地将{{1}中的if ((exampleArr as string[]).includes(e.key)) {} // okay 视为string[]中的covariant。这很适合阅读,但是在编写属性时会遇到问题:

Array<T>

但是由于您只是从数组中读取数据,因此非常安全。


好的,希望其中之一对您有所帮助。祝你好运!

答案 1 :(得分:0)

如果您要比较两种不同的类型,那么它们自然是不同的。

想象你有:

type A = {paramA: string};
type B = {paramB: number};

const valuesA: A[] = [{paramA: 'whatever'}];
const valueB: B = {paramB: 5};

valuesA.includes(valueB); // This will always be false, so it does not even make sense

在您的情况下,编译器威胁AllowedChars是与string完全不同的类型。您必须将收到的string“投射”到AllowedChars

  

但是这是正确的吗,我正在接受可能是任何东西的用户输入。

编译器不知道您要使用includes完成什么工作。它只知道它们具有不同的类型,因此不应进行比较。