我有这个:
interface Obj {
foo: string,
bar: number,
baz: boolean
}
想要的类型是这个元组:
[string, number, boolean]
如何将接口转换为元组?
更新:
我原来的问题是: 我制作了一些具有声明精神的自以为是的库,用户应该在其中用对象文字描述函数的参数。像这样:
let paramsDeclaration = {
param1: {
value: REQUIRED<string>(),
shape: (v) => typeof v === 'string' && v.length < 10
},
param2: {
value: OPTIONAL<number>(),
...
},
}
然后库获取这个对象并创建一个带有参数的函数:
(param1: string, param2?: number) => ...
所以,制作这样的功能不是问题,问题是正确type它,以便用户获得良好的代码完成(IntelliSense)。
附言我知道这是不可解决的,但知道什么是最接近的解决方法/黑客会很有趣。
90% 的时间你认为 Typescript 中的某些事情是不可能的,真正的答案是它 是可能的,但你可能不应该这样做。
这是一个使用
TuplifyUnion
的解决方案,来自this answer,它将联合类型转换为元组类型;请注意,我们需要从对象键的联合开始,不是它的值,因为值本身可能是联合(例如boolean
在技术上是true | false
)。
阅读链接的答案,详细说明
// oh boy don't do this
评论的含义。如果您希望 API 的用户指定 API 生成的函数的参数,那么明智的选择是首先在数组中接受这些参数规范。
type ObjValueTuple<T, KS extends any[] = TuplifyUnion<keyof T>, R extends any[] = []> =
KS extends [infer K, ...infer KT]
? ObjValueTuple<T, KT, [...R, T[K & keyof T]]>
: R
// type Test = [string, number, boolean]
type Test = ObjValueTuple<Obj>
不是问题的真正答案,但由于我实际上认为不可能做到,希望这至少在某种程度上有所帮助:
function REQUIRED<T>(): T {
//...
}
function OPTIONAL<T>(): T {
//...
}
interface ParamsDeclaration {
readonly [paramName: string]: {
readonly value: any;
readonly shape?: Function;
};
}
type Func<T> = T extends {
readonly [paramName: string]: {
readonly value: infer U;
};
} ? (...params: Array<U>) => void
: never;
function create<T extends ParamsDeclaration>(paramsDeclaration: T): Func<T> {
// ...
}
const paramsDeclaration = {
param1: {
value: REQUIRED<string>(),
shape: (v: any) => typeof v === 'string' && v.length < 10
},
param2: {
value: OPTIONAL<number>(),
//...
},
};
// Type is '(...params: (string | number)[]) => void'
const func1 = create(paramsDeclaration);
func1('1', 2); // Ok
func1(2, '1'); // Ok, but I assume not what you want
func1(Symbol()); // TS error
替代建议,
它需要设置参数的顺序。
interface Param {
readonly value: any;
readonly shape?: Function;
}
type Func<T extends Record<string, Param>, orders extends (keyof T)[]> = (...args:{
[key in keyof orders]:orders[key] extends keyof T ? T[orders[key]]['value']: orders[key];
})=>void;
function create<T extends Record<string, Param>, ORDERS extends (keyof T)[]>(params: T, ...orders:ORDERS): Func<T, ORDERS> {
return 0 as any;
}
const func1 = create({a:{value:0}, b:{value:''}, c:{value:true}}, 'a', 'b', 'c');
func1(0, '1', true); // ok
func1(true, 0, '1'); // error
或
带数组的参数声明
type Func2<T extends Param[]> = (...args:{
[key in keyof T]:T[key] extends Param ? T[key]['value'] : T[key]
})=>void;
function create2<T extends Param[], ORDERS extends (keyof T)[]>(...params: T): Func2<T> {
return 0 as any;
}
const func2 = create2({value:0}, {value:''}, {value:true});
func2(0, '1', true); // ok
func2(true, 0, '1'); // error
似乎共识是不可能的atm,但我还是想试试。无法保证顺序,因为对象不保留键顺序。
export type Partition<T> = UnionToTuple<
{ [K in keyof T]: { [k in K]: T[K] } }[keyof T]
>
type Pack<T> = T extends any ? (arg: T) => void : never
type Unpack<T> = [T] extends [(arg: infer I) => void] ? I : never
type Into<T> = Unpack<Unpack<Pack<Pack<T>>>>
type UnionToTuple<T> = Into<T> extends infer U
? Exclude<T, U> extends never
? [T]
: [...UnionToTuple<Exclude<T, U>>, U]
: never
type Data = { a0: 'foo'; b0: { b1: 'bar' } }
type Mock = Partition<Data>
[
{ a0: 'foo'; },
{ b0: { b1: 'bar'; }; }
]