【问题标题】:Specifying array of functions with unknown arguments throws a typeerror指定具有未知参数的函数数组会引发类型错误
【发布时间】:2021-10-01 22:39:40
【问题描述】:

我设置了一个名为useField.ts 的相当简单的钩子,它的定义方式如下:

type CheckFunction = <T>(input: T, ...args: unknown[]) => unknown

export const useField = <T>(
   input: T,
   ...checkFunctions: CheckFunction[]
) => {
  // .... and the code goes here, but irrelevant for the question
} 

之后我开始在useField.test.tsx 中通过以下方式编写测试:

import { renderHook } from '@testing-library/react-hooks'
import { useField } from '../useField'

describe('useField error functions', () => {
 
  const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/gi
  const isValidEmail = (input: string): boolean => !!input.match(emailRegex)

  test('Basic checkFunction', () => {
     const { result } = renderHook(() => useField('valid@email.com', isValidEmail)
     // ^ I get a type-error here
  })
})

我尝试通过isValidEmail 函数的行有以下 错误:

Argument of type '(input: string) => boolean' is not assignable to parameter of type 'CheckFunction'.
  Types of parameters 'input' and 'value' are incompatible.
    Type 'T' is not assignable to type 'string'.

现在可以通过将checkFunctions 的定义分配给any 从技术上解决这个问题,但我要提防这件事,因为它有点违背了类型检查的目的。

我也考虑过使用泛型,但问题是,参数可以是各种类型,并且不能用单个泛型的数组来表达

知道我在这里做错了什么吗?因为从逻辑的角度来看,unknown 的用法在这里似乎是正确的。

【问题讨论】:

    标签: typescript reactjs typescript react-hooks typescript-typings react-testing-library


    【解决方案1】:

    假设你的检查函数只接受一个参数,你为什么将它定义为接受多个值?例如。 ...args: unknown[]。您还可以指定它返回 boolean 而不仅仅是 unknown。

    你可以做这样的事情,它实际上使用了一个泛型,但据我所知,它似乎适用于你的用例:

    export const useField = <T>(
        input: T,
        ...checkFunctions: Array<(value: T) => boolean>
    ) => {
        // .... and the code goes here, but irrelevant for the question
    }
    
    const result = useField('someString', validator1, validator2);
    // ^ It would know T is a string here, since the input is a string
    // Therefore it'll check that `validator1` is of the type `(value: string) => boolean`
    const result = useField('someString', str => {
        // TypeScript knows the type of `str` is string
        return str.includes('something');
    });
    // This would error:
    const result = useField<string>(123, validator1);
    // As well as this due to the validator accepting the wrong value:
    const result = useField('string', (value: number) => true);
    

    这是假设 useField 需要实际输入的值,而不是 &lt;input&gt; 元素的 ID/名称。否则没有理由让input 是通用的(毕竟,名称/id 始终是一个字符串)并且你不会命名&lt;input&gt; valid@email.com 而是email。


    对于问题中的更新代码。

    您错误地定义了“通用”CheckFunction 类型。改用这个:

    type CheckFunction<T> = (input: T, ...args: unknown[]) => unknown
    
    export const useField = <T>(
        input: T,
        ...checkFunctions: CheckFunction<T>[]
    ) => {
        // .... and the code goes here, but irrelevant for the question
    }
    

    您使用type Type = &lt;T&gt;(... 的版本基本上告诉TypeScript“此函数具有a 泛型类型,但在Type 之外无关紧要”。如果你使用type Type&lt;T&gt;,你告诉 TypeScript 该类型很重要,例如它应该由该类型的用户指定。

    简而言之,通过使用type Type&lt;T&gt;,您可以让您的useField 指定Type 中的T 代表什么,例如与useField的T同类型。

    【讨论】:

    • 因为它将用于一个包,我需要为用户留出余地来传递多个参数和结果返回(例如,抛出 error (void) 的返回仍然被认为是有效的。就像带有参数(input, '&gt;=', 20) =&gt; {//...} 的函数必须是定义的有效checkFunction。这也是我没有使用原始帖子中提到的泛型的原因。所以这里的参数有不同的类型,并且有多个(因此类型设置为...args: unknown[],因为我们不知道参数的数量。
    • throw ... 语句不关心函数的返回类型是什么。毕竟,如果函数抛出,它实际上并没有返回任何东西。至于你的最后一个例子,我想你想做useField(input, '&gt;=', 20, expressionValidator) 什么的?你如何区分参数和验证器? _假设我希望能够将函数作为参数传递给另一个验证器,例如useField(input, '&lt;=', validateEmail, validateArrayWithValidator).
    • 我可能应该指定(回想起来甚至是类型定义的),第一个参数将始终是输入类型。所以现在,我想,定义应该更类似于type CheckFunction = &lt;T&gt;(input: T, ...args?: unknown[]) =&gt; unknown,但这仍然给我留下一个错误“Type 'T' is not assignable to type 'string'”
    • 你能用更新的代码和错误更新问题吗?无法弄清楚你到底做了什么(因此错误是关于什么的)
    • 绝对。你去 :-) 我非常困惑为什么它不起作用。因为我会理解 ...args 传播是否存在问题,但我没想到 ts 也会抛出关于泛型的错误。
    猜你喜欢
    • 2018-06-13
    • 2015-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多