Provide an operator to be used in both {} types and interface types which inherits a set of properties from another object type whilst also allowing for those properties to be selectively overridden.
type SizeProps = { width: number, height: number }
type MyProps = {
...SizeProps,
width: number | string,
foo: boolean,
}
// Also works with interfaces
interface IProps {
...SizeProps;
width: number | string;
foo: boolean;
}
In the example above, the MyProps type would resolve to the following:
{ width: number | string, height: number, foo: boolean }
This behavior can already be achieve with the following mapped type, but it's not as elegant (especially once formatted with Prettier):
type Merge<A, B> = {
[K in keyof A]: K extends keyof B ? B[K] : A[K]
} & B
type SizeProps = { width: number, height: number }
type MyProps = Merge<
SizeProps,
{
width: number | string,
foo: boolean,
}
>
Also, the Merge type has its limitations:
My suggestion meets these guidelines:
@jcalz I've seen that proposal. Thanks for linking it. I believe it to be complementary to this one.
Using only the "spread type", my example above would look like this:
type SizeProps = { width: number, height: number }
type MyProps = SizeProps ... {
width: number | string,
foo: boolean,
}
Note: Interfaces are not supported with the "spread type" in #10727.
My proposal (1) provides a more elegant (IMO) syntax for "type merging" and (2) provides a way to merge an object type into an interface whilst allowing property overrides.
As far as I know #10727 would support both A ... {x: B} and {...A, x: B} syntaxes.
If you want to make the type "flat" you can use a mapped type (and conditional type inference) to do this:
type Merge<A, B> = ({ [K in keyof A]: K extends keyof B ? B[K] : A[K] } &
B) extends infer O
? { [K in keyof O]: O[K] }
: never;
type SizeProps = { width: number; height: number };
type OtherProps = { width: number | string; foo: boolean };
type MyPropsType = Merge<SizeProps, OtherProps>;
/*
type MyPropsType = {
height: number;
width: string | number;
foo: boolean;
}
*/
Note that Merge doesn't necessarily do the right things with optional properties, since presumably Merge<{a: string}, {a?: number}> should become something like {a: string | number | undefined}. The proposal in #10727 goes into detail about what should happen in such cases.
All I'm seeing here that's not a duplicate of #10727 is the ability to use a spread operator to define an interface. But I wouldn't expect interfaces to ever support type operators directly (someone can correct me if I'm wrong). Instead I'd expect to continue to use extends and/or declaration merging to compose interface types.
In the above case, since you have an object type (or an intersection of object types) whose members are statically known (that is, not dependent on some unresolved generic), you can promote it to an interface via extends:
interface MyProps extends Merge<SizeProps, OtherProps> {} // okay
const myProps: MyProps = {
height: 1,
width: "one",
foo: true
};
I'm just an ~obnoxious~ interested bystander, though, and don't speak for the actual TS maintainers... I expect someone will come along to give a more authoritative assessment here. Good luck!
Let's track this at #10727 (since they are motivated by the same use cases and have the same solutions) and have the syntax discussion happen over there. Thanks!
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Most helpful comment
As far as I know #10727 would support both
A ... {x: B}and{...A, x: B}syntaxes.If you want to make the type "flat" you can use a mapped type (and conditional type inference) to do this:
Note that
Mergedoesn't necessarily do the right things with optional properties, since presumablyMerge<{a: string}, {a?: number}>should become something like{a: string | number | undefined}. The proposal in #10727 goes into detail about what should happen in such cases.All I'm seeing here that's not a duplicate of #10727 is the ability to use a spread operator to define an
interface. But I wouldn't expect interfaces to ever support type operators directly (someone can correct me if I'm wrong). Instead I'd expect to continue to useextendsand/or declaration merging to compose interface types.In the above case, since you have an object type (or an intersection of object types) whose members are statically known (that is, not dependent on some unresolved generic), you can promote it to an interface via
extends:I'm just an ~obnoxious~ interested bystander, though, and don't speak for the actual TS maintainers... I expect someone will come along to give a more authoritative assessment here. Good luck!
Playground link