-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Added section on excess property checking #148
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ In TypeScript, interfaces fill the role of naming these types, and are a powerfu | |
| The easiest way to see how interfaces work is to start with a simple example: | ||
|
|
||
| ```ts | ||
| function printLabel(labelledObj: {label: string}) { | ||
| function printLabel(labelledObj: { label: string }) { | ||
| console.log(labelledObj.label); | ||
| } | ||
|
|
||
|
|
@@ -20,6 +20,7 @@ printLabel(myObj); | |
| The type-checker checks the call to `printLabel`. | ||
| The `printLabel` function has a single parameter that requires that the object passed in has a property called `label` of type string. | ||
| Notice that our object actually has more properties than this, but the compiler only checks that *at least* the ones required are present and match the types required. | ||
| There are some cases where TypeScript isn't as lenient as we'll go over in a bit. | ||
|
|
||
| We can write the same example again, this time using an interface to describe the requirement of having the `label` property that is a string: | ||
|
|
||
|
|
@@ -82,7 +83,7 @@ interface SquareConfig { | |
| width?: number; | ||
| } | ||
|
|
||
| function createSquare(config: SquareConfig): {color: string; area: number} { | ||
| function createSquare(config: SquareConfig): { color: string; area: number } { | ||
| let newSquare = {color: "white", area: 100}; | ||
| if (config.color) { | ||
| // Error: Property 'collor' does not exist on type 'SquareConfig' | ||
|
|
@@ -97,6 +98,61 @@ function createSquare(config: SquareConfig): {color: string; area: number} { | |
| let mySquare = createSquare({color: "black"}); | ||
| ``` | ||
|
|
||
| # Excess Property Checks | ||
|
|
||
| In our first example using interfaces, TypeScript was okay with giving a value with the type `{ size: number; label: string; }` to something that only expected a `{ label: string; }`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
"... TypeScript let us pass |
||
| We also just learned about optional properties, and how they're useful when describing so-called "option bags". | ||
|
|
||
| However, combining the two naively would let you to shoot youreslf in the foot the same way you might in JavaScript. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo:yourself |
||
| For example, taking our last example using `createSquare`: | ||
|
|
||
| ```ts | ||
| interface SquareConfig { | ||
| color?: string; | ||
| width?: number; | ||
| } | ||
|
|
||
| function createSquare(config: SquareConfig): { color: string; area: number } { | ||
| // ... | ||
| } | ||
|
|
||
| let mySquare = createSquare({ colour: "red", width: 100 }); | ||
| ``` | ||
|
|
||
| Notice the given argument to `createSquare` is spelled *`colour`* instead of `color`. | ||
| In plain JavaScript, this sort of thing fails silently. | ||
|
|
||
| One could argue that this program is correctly typed, since the `width` properties are compatible, there's no `color` property present, and the extra `colour` property is insignificant. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be "You could argue ..." to be consistent with the style of the rest of the file. |
||
|
|
||
| However, TypeScript is more opinionated here, and takes the stance that there's probably a bug in this code. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can simplify this down to "However, TypeScript thinks that there is probably a bug in this code." |
||
| Object literals get special treatment and undergo *excess property checking* when assigning them to other variables, or passing them as arguments. | ||
| If an object literal has any properties that the "target type" doesn't have, you'll get an error. | ||
|
|
||
| ```ts | ||
| // error: 'colour' not expected in type 'SquareConfig' | ||
| 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: | ||
|
|
||
| ```ts | ||
| let mySquare = createSquare({ colour: "red", width: 100 } 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think this should be our recommendation; the recommendation would be to either use cast or use a contextual type with union types for all possible variations.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And probably mention the compiler flag to disable the error reporting. |
||
|
|
||
| Keep in mind, for simple code like above, it's questionable whether you should really be trying to "get around" these checks. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify to "Keep in mind that for simple code you shouldn't really be trying to "get around" these checks." |
||
| For more complex objects literals that have methods and hold state, you may need to keep these techniques in mind, but a majority of excess property errors in user code are actually bugs. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extra plural, should be: "object literals" |
||
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example or explanation? I'm not sure what you mean here. |
||
|
|
||
| # Function Types | ||
|
|
||
| Interfaces are capable of describing the wide range of shapes that JavaScript objects can take. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "... isn't as lenient, which we'll cover in a bit." is easier to read.