Reputation: 13
TypeScript allows defining interfaces for objects that can contain any property:
interface NumberHash {
[key: string]: number;
}
let numbers: NumberHash = {
zero: 0,
one: 1,
pi: 3.14
};
When looking up properties from such an object, they are returned as number
s, as is expected.
let key = 'pi';
let pi = numbers[key];
console.log(pi.toFixed(1)); // all is well
However, when looking up unknown properties, they are still returned as number
s, even though they are actually undefined
.
let key = 'foo';
let foo = numbers[key]; // `foo` becomes `undefined`
console.log(foo.toFixed(1)); // errors at runtime
The code above throws a TypeError
at runtime, but not during compilation. Why does TypeScript not catch this error during compilation?
Upvotes: 1
Views: 299
Reputation: 23692
With the strictNullChecks
compiler option, the interface NumberHash
could be declared as below:
interface NumberHash {
[key: string]: number | undefined;
}
Then:
let numbers: NumberHash = {};
let foo = numbers['foo'];
console.log(foo.toFixed(1)); // error at compile time: Object is possibly 'undefined'
See also:
Upvotes: 1
Reputation: 12804
In your example, it's quite clear what properties will and will not exist on your NumberHash
object - they're explicitly specified in the object literal that is assigned to your numbers
variable. It's reasonable to think that this is something TypeScript could guard against at compile-time.
However, take a more dynamic example:
interface NumberHash {
[key: string]: number;
}
let numbers: NumberHash = {};
const max = Math.random() * 100;
for (var i = 0; i < max; i++) {
numbers[i.toString()] = i;
}
let foo = numbers["42"];
// is foo undefined? Who knows! Depends
// on what Math.random() returned.
console.log(foo.toFixed(1));
At compile time, the compiler can't know what will be returned by Math.random()
at runtime. Because the compiler must handle both your example and my example, it leaves it up to the developer to ensure the key exists.
If you know the properties that will exist in your NumberHash
object at compile time, you can rewrite this data structure as a class:
class Numbers {
public static readonly zero: number = 0;
public static readonly one: number = 1;
public static readonly pi: number = Math.PI;
}
// both of these will compile without errors
let foo = Numbers['zero'];
let bar = Numbers.one;
// Compiler error: Element implicitly has an 'any' type
// because type 'typeof Numbers' has no index signature.
let baz = Numbers['foo'];
Upvotes: 0