From 9292d558cd4e897690dffed47981705f245e80d2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 25 Mar 2016 23:40:39 -0700 Subject: [PATCH 1/3] Suggest index signatures for excess properties/update index signatures. --- pages/Interfaces.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pages/Interfaces.md b/pages/Interfaces.md index 2498c1204..c46057045 100644 --- a/pages/Interfaces.md +++ b/pages/Interfaces.md @@ -149,6 +149,9 @@ let mySquare = createSquare(squareOptions); Since `squareOptions` won't undergo excess property checks, the compiler won't give you an error. +One final way to get around the check is to add an index signature if you are sure that the object will *always* have properties with unknown names. +We'll discuss index signatures in a bit. + Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. That means if you're running into excess property checking problems for something like option bags, you might need to revise some of your type declarations. @@ -218,10 +221,11 @@ mySearch = function(src, sub) { } ``` -# Array Types +# Indexable Types -Similarly to how we can use interfaces to describe function types, we can also describe array types. -Array types have an `index` type that describes the types allowed to index the object, along with the corresponding return type for accessing the index. +Similarly to how we can use interfaces to describe function types, we can also describe types that we treat a bit like arrays. +Indexable types have a signature called an *index signature* that describes the types we can use to index into the object, along with the corresponding return type for when we do perform indexing accesses. +Let's take an example: ```ts interface StringArray { @@ -230,14 +234,19 @@ interface StringArray { let myArray: StringArray; myArray = ["Bob", "Fred"]; + +let myStr: string = myArray[0]; ``` -There are two types of supported index types: string and number. +Above, we have a `StringArray` interface that defines an index signature. +This index signature states that when a `StringArray` is indexed with a `number`, it will return a `string`. + +There are two types of supported index signatures: string and number. It is possible to support both types of indexers, with the restriction that the type returned from the numeric indexer must be a subtype of the type returned from the string indexer. While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. This is because a string index declares that `obj.property` is also available as `obj["property"]`. -In this example, `name`'s type does not match the string index's type, and the type-checker gives an error: +In the following example, `name`'s type does not match the string index's type, and the type-checker gives an error: ```ts interface NumberDictionary { From 78770f8d9be943b806e3b9158d691969af0bde84 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 31 Mar 2016 01:16:13 -0700 Subject: [PATCH 2/3] Update Interfaces.md --- pages/Interfaces.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/pages/Interfaces.md b/pages/Interfaces.md index c46057045..fc8e38938 100644 --- a/pages/Interfaces.md +++ b/pages/Interfaces.md @@ -149,8 +149,18 @@ let mySquare = createSquare(squareOptions); Since `squareOptions` won't undergo excess property checks, the compiler won't give you an error. -One final way to get around the check is to add an index signature if you are sure that the object will *always* have properties with unknown names. -We'll discuss index signatures in a bit. +One final way to get around the check is to add a string index signature if you are sure that the object will *always* have properties with unknown names. +If `SquareConfig`s always had `color` and `width` properties with the above types, but could *also* have any number of other properties, then we could define it as the following: + +```ts +interface SquareConfig { + color?: string; + width?: number; + [propName: string]: any; +} +``` + +We'll discuss index signatures in a bit, but here we're saying a `SquareConfig` can have any number of properties of any types. Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. @@ -223,8 +233,8 @@ mySearch = function(src, sub) { # Indexable Types -Similarly to how we can use interfaces to describe function types, we can also describe types that we treat a bit like arrays. -Indexable types have a signature called an *index signature* that describes the types we can use to index into the object, along with the corresponding return type for when we do perform indexing accesses. +Similarly to how we can use interfaces to describe function types, we can also describe types that we can "index into" like `a[10]`, or `ageMap["daniel"]`. +Indexable types have something called an *index signature* that describes the types we can use to index into the object, along with the corresponding return types when indexing. Let's take an example: ```ts @@ -242,7 +252,24 @@ Above, we have a `StringArray` interface that defines an index signature. This index signature states that when a `StringArray` is indexed with a `number`, it will return a `string`. There are two types of supported index signatures: string and number. -It is possible to support both types of indexers, with the restriction that the type returned from the numeric indexer must be a subtype of the type returned from the string indexer. +It is possible to support both types of indexers, but the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. +This is because when indexing with a `number`, JavaScript will actually convert that to a `string` before indexing into an object. +That means that indexing with `100` (a `number`) is the same thing as indexing with `"100"` (a `string`), so the two need to be consistent. + +```ts +class Animal { + name: string; +} +class Dog extends Animal { + breed: string; +} + +// Error: indexing with a 'string' will sometimes get you a Dog! +interface NotOkay { + [x: number]: Animal; + [x: string]: Dog; +} +``` While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. This is because a string index declares that `obj.property` is also available as `obj["property"]`. From 43f492dc80ea4c374a105c053122ed8aac9182ea Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 31 Mar 2016 15:46:15 -0700 Subject: [PATCH 3/3] Update Interfaces.md --- pages/Interfaces.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pages/Interfaces.md b/pages/Interfaces.md index fc8e38938..791476416 100644 --- a/pages/Interfaces.md +++ b/pages/Interfaces.md @@ -134,23 +134,14 @@ let mySquare = createSquare({ colour: "red", width: 100 }); ``` Getting around these checks is actually really simple. -The best and easiest method is to just use a type assertion: +The easiest method is to just use a type assertion: ```ts -let mySquare = createSquare({ colour: "red", width: 100 } as SquareConfig); +let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig); ``` -Another approach, which might be a bit surprising, is to assign the object to another variable: - -```ts -let squareOptions = { colour: "red", width: 100 }; -let mySquare = createSquare(squareOptions); -``` - -Since `squareOptions` won't undergo excess property checks, the compiler won't give you an error. - -One final way to get around the check is to add a string index signature if you are sure that the object will *always* have properties with unknown names. -If `SquareConfig`s always had `color` and `width` properties with the above types, but could *also* have any number of other properties, then we could define it as the following: +However, a better approach to might to add a string index signature if you're sure that the object can have some extra properties that are used in some special way. +If `SquareConfig`s can have `color` and `width` properties with the above types, but could *also* have any number of other properties, then we could define it like so: ```ts interface SquareConfig { @@ -160,7 +151,15 @@ interface SquareConfig { } ``` -We'll discuss index signatures in a bit, but here we're saying a `SquareConfig` can have any number of properties of any types. +We'll discuss index signatures in a bit, but here we're saying a `SquareConfig` can have any number of properties, and as long as they aren't `color` or `width`, their types don't matter. + +One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable: +Since `squareOptions` won't undergo excess property checks, the compiler won't give you an error. + +```ts +let squareOptions = { colour: "red", width: 100 }; +let mySquare = createSquare(squareOptions); +``` Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. @@ -234,7 +233,7 @@ mySearch = function(src, sub) { # Indexable Types Similarly to how we can use interfaces to describe function types, we can also describe types that we can "index into" like `a[10]`, or `ageMap["daniel"]`. -Indexable types have something called an *index signature* that describes the types we can use to index into the object, along with the corresponding return types when indexing. +Indexable types have an *index signature* that describes the types we can use to index into the object, along with the corresponding return types when indexing. Let's take an example: ```ts @@ -248,7 +247,7 @@ myArray = ["Bob", "Fred"]; let myStr: string = myArray[0]; ``` -Above, we have a `StringArray` interface that defines an index signature. +Above, we have a `StringArray` interface that has an index signature. This index signature states that when a `StringArray` is indexed with a `number`, it will return a `string`. There are two types of supported index signatures: string and number.