Reputation: 4014
Here is a simple case that is easy in isolation, but breaks down when composed into a larger type.
If scrollToItem
is defined, then getRowId
must also be defined. Otherwise, getRowId
is optional.
This type definition works just fine:
type ScrollProps<T> =
| { scrollToItem: T; getRowId: (row: T) => string; }
| { getRowId?: (row: T) => string; };
const a: ScrollProps<number> = {}; // ok
const b: ScrollProps<number> = { scrollToItem: 6, getRowId: (row) => '' }; // ok
const c: ScrollProps<number> = { getRowId: (row) => '' }; // ok
const d: ScrollProps<number> = { scrollToItem: 6 }; // error - expected
It can be extended further to pick the getRowId
property from an imported library type.
type LibraryObject<T> = {
getRowId?: (row: T) => string;
otherProps?: boolean;
};
type ScrollProps<T> =
| ({ scrollToItem: T } & Required<Pick<LibraryObject<T>, 'getRowId'>>)
| Pick<LibraryObject<T>, 'getRowId'>;
const a: ScrollProps<number> = {}; // ok
const b: ScrollProps<number> = { scrollToItem: 6, getRowId: (row) => '' }; // ok
const c: ScrollProps<number> = { getRowId: (row) => '' }; // ok
const d: ScrollProps<number> = { scrollToItem: 6 }; // error - expected
Unfortunately, I have to add this type definition onto an existing type, which is already extending the library type. For some reason, TypeScript no longer requires getRowId
when passing scrollToItem
IF ALSO passing some of the other properties it extends.
type LibraryObject<T> = {
getRowId?: (row: T) => string;
otherProps?: boolean;
};
type ScrollProps<T> =
| ({ scrollToItem: T } & Required<Pick<LibraryObject<T>, 'getRowId'>>)
| Pick<LibraryObject<T>, 'getRowId'>;
type MasterType<T> = LibraryObject<T> & ScrollProps<T>;
const a: MasterType<number> = {}; // ok
const b: MasterType<number> = { scrollToItem: 6, getRowId: (row) => '' }; // ok
const c: MasterType<number> = { getRowId: (row) => '' }; // ok
const d: MasterType<number> = { scrollToItem: 6 }; // error - expected
const e: MasterType<number> = { scrollToItem: 6, otherProps: true }; // NO ERROR, but I wish there were! 🙁
Why would setting any of the other properties negate the required properties managed by ScrollProps
?
Upvotes: 1
Views: 49
Reputation: 4014
The comments on this question led me to an answer. I don't fully understand, but TypeScript seems to allow partial implementation of Type contracts. Updating the ScrollProps
type to use Partial<Record<'scrollToItem', never>>
in the second union option gives the expected results.
type ScrollProps =
| ({ scrollToItem: number } & Required<Pick<LibraryObject, 'getRowId'>>)
| (Partial<Record<'scrollToItem', never>> & Pick<LibraryObject, 'getRowId'> );
const d: MasterType = { scrollToItem: 6 }; // error - expected
const e: MasterType = { otherProps: true, scrollToItem: 6 }; // gives an error now
Upvotes: 1