Typescript:对象类型转换为数组类型(元组)

13

我有这个:

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) => ...

因此,编写这样的函数并不是问题,问题在于正确地类型,以便用户获得良好的代码完成(IntelliSense)。

附言:我知道这是无法解决的,但了解最接近的可能解决方法/技巧会很有趣。


你是否认为顺序很重要?我认为这是不可能的。 - Titian Cernicova-Dragomir
2
你能多说一些关于你的原始问题吗?也许我们可以找到另一个解决方案。 - Matt McCutchen
当你说“转换”时,你是指要创建一个与匹配接口的对象相匹配的元组类型数组吗?还是你想要改变Obj接口成为元组类型? - Sean Sobey
你能否将 paramsDeclaration 结构化为一个数组而不是一个对象?如果你想要参数的名称,你可以将它们放在子对象的字段中。 - Matt McCutchen
TS的类型系统是图灵完备的(我只是读了一篇博客,没有深入理解)。 - justTryIt
显示剩余6条评论
4个回答

6

在Typescript中,当你认为某件事情是不可能的时候,90%的情况下实际上它是可以实现的,只是你可能不应该这样做。

以下是使用这个答案中的TuplifyUnion解决方案,可以将联合类型转换为元组类型;请注意,我们需要从对象键的联合类型开始,而不是其值,因为这些值本身可能是联合类型(例如,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>

点击链接访问 Playground


有没有办法给生成的元组命名?比如对于 { a: string, b: number, c: boolean }Obj,可以像 [a: string, b: number, c: boolean] 这样命名吗? - Yihao Gao
@YihaoGao 我不相信有任何编程方式可以设置元组元素的名称。 - kaya3

3

虽然这并不是对问题的直接回答,但我认为这是有帮助的,因为我认为这是不可能做到的:

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

1
谢谢您花费时间!了解您的方法很有趣。是的,不幸的是,顺序很重要,但似乎没有办法推断接口/对象属性的顺序。 - Nurbol Alpysbayev

2

备选建议,
需要设置参数的顺序。

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

或者
使用数组的ParamDeclarations

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

0

看起来目前似乎没有可能,但我仍然想尝试。由于对象不保留关键字顺序,因此无法保证顺序。

解决方案

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'; }; }
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接