Reputation: 4718
I have an example of Typescript with type guard and function rest parameters:
interface BaseProps {
username: string;
password: string;
}
type Props = BaseProps & (
| {isAdmin: true, adminName: string}
| {isAdmin: false}
)
// Doesn't works
const myFn = ({isAdmin, ...rest}: Props) => {
if(isAdmin === true) {
rest.adminName; // Property 'adminName' does not exist on type '{ username: string; adminName: string; } | { username: string; }'.
}
}
// It works
const myFn2 = (args: Props) => {
if(args.isAdmin === true) {
args.adminName;
}
}
What is wrong with the rest parameters and type guard?
UPDATE SOLUTION:
I found the solution, using Assert Functions
to resolve the issue.
declare function assertType<T>(val: unknown): asserts val is T;
const myFn = ({password, isAdmin, ...rest}: Props) => {
if(isAdmin === true) {
assertType<Omit<Props & {isAdmin: true}, keyof Props>>(rest);
rest.adminName; // <=== HERE
rest.username;
// Should be error
rest.password;
}
}
Upvotes: 0
Views: 212
Reputation: 1222
The type of Props
is
{ username: string, isAdmin: true, adminName: string } | { username: string, isAdmin: false }
When you assert the value of isAdmin
on an object of type Prop
(e.g. an if
statement), typescript is then able to narrow down the actual type between { username: string, isAdmin: true, adminName: string }
and { username: string, isAdmin: false }
.
If you remove isAdmin
by destructuring, the type of rest
becomes
{ username: string, adminName: string } | { username: string }
The values isAdmin
and rest
become uncorrelated and the only way to narrow down the type of rest
is to assert the presence of the adminName
property.
Maybe you shouldnt use rest parameters for this specific case. You could also manually cast the rest
variable inside the if
.
Upvotes: 0