Reputation: 4656
First I have a type definition map all properties into number by using keyof
type Numeric<T> = {
[K in keyof T]: number
}
Below is a class I will use.
class Entity {
aNumber: number;
}
Below is a function which accept a generic type argument and a local variable with type Numberic<T>
. But when I assigned { aNumber: 1 }
it gave a compile error.
const fn = <T extends Entity>() => {
const n: Numeric<T> = {
// ^
// Type '{ aNumber: number; }' is not
// assignable to type 'Numeric<T>'
aNumber: 1
};
};
I don't know why { aNumber: number; }
cannot be assigned to Numeric<T>
since the type argument T
must be extended from Entity
and it must contains a key named aNumber
. This means aNumber
must be the key of type T and should be able to assigned to Numeric<T>
.
Upvotes: 0
Views: 164
Reputation: 19947
The constraint imposed by <T extends Entity>
should be considered just the minimum requirement that T
should meet. It means "T
should at least contain aNumber: number
pair".
Let's see const n: T
. It means "n should at least contains whatever key-value pairs that are in T
".
Now we do know, T
has a aNumber: number
pair, but remember that's just the minimum requirement. T
might as well be { aNumber: number; aString: string }
. That's why this will also give you error.
// if you understand this:
const n: { aNumber: number; aString: string } = { aNumber: 42 } // error, of course
// you see why this is an error:
const n: T = { aNumber: 42 } // also error
You can never tell what exactly is T
. keyof T
is fine though, cus we know about at least one of T
's key for sure.
const k: keyof T = "aNumber"
To prove my point, let's review a non-generic case. Take @basarat's code for example, here X
is not genreic.
type Numeric<T> = {
[K in keyof T]: number
}
type OptionalNumeric<T> = {
[K in keyof T]?: number
}
interface Entity {
aNumber: number
}
interface X extends Entity {
notANumber: string
}
// Error, because `notANumber` is missing
const o: Numeric<X> = { aNumber: 1 };
// Correct
const o1: Numeric<X> = { aNumber: 1, notANumber: 2 };
// Also correct, because all keys are optional.
const o2: OptionalNumeric<X> = { aNumber: 1 };
Side note. Above should explain your case. However, I do believe there's a bug in TS.
I think if you use OptionalNumeric
in your original case, what you want should work. But turns out it doesn't. Should be a defect when generic type parameters involve.
Upvotes: 1
Reputation: 276171
The error message is misleading. However there is an error and that is what TypeScript is catching. Indeed there is nothing wrong with Entity
:
type Numeric<T> = {
[K in keyof T]: number
}
interface Entity {
aNumber: number
}
// No error
const n: Numeric<Entity> = {
aNumber: 1
};
However when you say T extends Entity
it opens it up for non number values e.g.
type Numeric<T> = {
[K in keyof T]: number
}
interface Entity {
aNumber: number
}
// No error
const n: Numeric<Entity> = {
aNumber: 1
};
interface X extends Entity {
notANumber: string
}
// Error. Thank you TypeScript
const o: Numeric<X> = {
aNumber: 1
};
Hence the error when you use a T extends Entity
in Numeric
.
Upvotes: 3