英文:
Correctly type arguments to callback function, based on first argument array types
问题
在TypeScript中,我有一个问题。
实际上,我正在尝试实现useSWR的功能,在其中可以将一个参数数组作为函数的第一个参数传递,并在第二个参数中传递一个回调函数,其中数组项成为第二个回调函数的参数。
以下是一个示例:
const fetcher = async (arg1: string, arg2: string): Promise<string | boolean> => {
try {
return arg1 + ' - ' + arg2;
} catch (e) {
return false;
}
}
type Fetcher<T, A extends any[]> = (...args: A) => Promise<T>;
const useAdminEndpoint = <T, A extends any[]>(key: A, fetcher: Fetcher<T, A>) => {
const run = async () => {
try {
return await fetcher.apply(null, key);
} catch (e) {
return false;
}
}
return result;
}
const result = useAdminEndpoint(['hello', '2'], (h, g) => fetcher(h, g));
参数的类型只是数组项的类型,作为元组,而不是例如arg0 = array[0],arg1 = array[1]等。
我不太确定我试图实现的术语,所以很难找到要搜索的内容。是否可能使参数的顺序根据数组中的项来推断其类型?
英文:
I have an issue in TypeScript.
Effectively I'm trying to achieve what useSWR do, in which you can pass an array of arguments as the first argument in a function, and in the second argument you send in a callback function where the array items become the arguments in the second callback.
Here is an example:
const fetcher = async(arg1: string, arg2: string): Promise<string | boolean> => {
try {
return arg1 + ' - ' + arg2
} catch(e) {
return false
}
}
type Fetcher<T, A extends any[]> = (...args: A) => Promise<T>
const useAdminEndpoint = <T, A extends any[]>(key: A, fetcher: Fetcher<T, A>) => {
const run = async() => {
try {
return await fetcher.apply(null,key)
} catch(e) {
return false
}
}
return result
}
const result = useAdminEndpoint(['hello',2],(h,g) => fetcher(h,g))
The type on the parameters is just the type of the items in the array as a tuple, instead of say arg0 = array[0], arg1 = array[1] etc.
I'm not really sure on the terminology around what I'm trying to achieve here so it's hard to find what to search. Is it possible even to have the order of arguments infer their type from the items in the array in order?
答案1
得分: 1
问题在于传递的参数数组类型被扩展为某种类型。例如:
const arr = ['hello', 1];
// (string | number)[]
type Arr = typeof arr;
在您的函数中也发生了同样的情况。可以通过进行const断言来修复此问题,这可以防止编译器扩展类型,因为使用const断言,我们定义了数组不会更改并且是readonly
:
const arr = ['hello', 1] as const;
// readonly ["hello", 1]
type Arr = typeof arr;
在函数中使用const断言并不是那么好,自从 TypeScript 5.0 中引入了const类型参数之后,可以更加优雅地完成相同的任务,这本质上就是const断言。这允许实现与在每个函数调用中添加烦人的as const
相同的结果:
type Fetcher<T, A extends readonly any[]> = (...args: A) => Promise<T>;
const useAdminEndpoint = <T, const A extends readonly any[]>(
key: A,
fetcher: Fetcher<T, A>,
) => {
const run = async () => {
try {
return await fetcher(...key);
} catch (e) {
return false;
}
};
return run();
};
由于const断言
和const类型参数
将数组转换为readonly
,因此我们也更新了条件(any[]
-> readonly any[]
)。
测试:
const result = useAdminEndpoint(['hello', 'world'], (h, g) => fetcher(h, g));
const result2 = useAdminEndpoint(['hello', 2], (h, g) => fetcher(h, g)); // 预期错误。第二个参数应为字符串
在线演示。
英文:
The problem is that type of passed array of arguments is widened to some type. For example:
const arr = ['hello', 1];
// (string | number)[]
type Arr = typeof arr;
The same thing happens in your function. This can be fixed by doing const assertion, which prevents the compiler from widening the type, since with const assertion we define that the array won't change and it is readonly
:
const arr = ['hello', 1] as const;
// readonly ["hello", 1]
type Arr = typeof arr;
In terms of the function using the const assertion is not that nice and it can be done elegantly since typescript 5.0, where const type parameters were added, which are doing basically const assertion. This allows to achieve the same result as with adding annoying as const
on every function call:
type Fetcher<T, A extends readonly any[]> = (...args: A) => Promise<T>;
const useAdminEndpoint = <T,const A extends readonly any[]>(
key: A,
fetcher: Fetcher<T, A>,
) => {
const run = async () => {
try {
return await fetcher(...key);
} catch (e) {
return false;
}
};
return run();
};
Since const assertion
and const type parameters
would turn the array to readonly
, we are updating the conditions as well (any[]
-> readonly any[]
)
Testing:
const result = useAdminEndpoint(['hello', 'world'], (h, g) => fetcher(h, g));
const result2 = useAdminEndpoint(['hello', 2], (h, g) => fetcher(h, g)); // expected error. arg 2 should be a string
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论