-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 12 #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: initial
Are you sure you want to change the base?
Chapter 12 #12
Conversation
|
||
While most of the time we want to have our types remain static, it is possible to create variables that can dynamically change their type like in JavaScript. This can be done with a technique called the "evolving `any`" which takes advantage of how variables are declared and inferred when no type is specified. | ||
|
||
To start, use `let` to declare the variable without a type, and TypeScript will infer it as `any`: |
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.
it's actually not any, it's magic ;p
it's true that internally TS uses a special kind of any
to track this:
var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType, "auto");
and it's true that it can be (confusingly) displayed as any
in tooltips but it's not any
. If it would be any
then the return type of this function should be any
, right?
function test() {
let foo;
return foo;
}
But the return type here isn't any - it's undefined
:) Matching what we can observe here
The fact that any
is sometimes displayed is because you check that a write positions - and we can write to it... well, anything. But checking such variables at read positions displays the correct/current type.
I might be really nit-picky here - especially given how it's displayed in tooltips. But how it's displayed in tooltip is a potential subject to change (there is an issue about it!). So in the future, the any-ness of it might not even be user-facing at all. It being any-like in any context is just a... minutiae, an implementation detail really.
// ^? | ||
``` | ||
|
||
Even without specifying types, TypeScript is incredibly smart about picking up on your actions and the behavior you're pushing to evolving `any` types. |
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.
a nice gotcha with this pattern:
const arr = [];
arr.push(1);
const obj = { arr };
// ^? const obj: { arr: number[]; }
arr.push("");
Oops.
timeOut: 1000, | ||
}; | ||
|
||
fetch(options); // No error! |
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.
this is not a good example because this is actually an error! (TS playground)
Type '{ timeOut: number; }' has no properties in common with type '{ timeout?: number | undefined; }'.(2559)
The parameter type here is a weak type - so whatever we try to pass in there has to have some overlap with it.
|
||
The issue is that the excess properties warning can often make you think TypeScript uses closed objects. | ||
|
||
But really, the excess properties warning is more like a courtesy. It's only used in cases where the object can't be modified elsewhere. |
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.
- it's a reason why we can have renaming suggestions in those errors like:
Object literal may only specify known properties, but 'timeOut' does not exist in type '{ timeout?: number | undefined; a: number; }'. Did you mean to write 'timeout'?(2561)
Without the excess property checks (and with open objects) this wouldn't be an error so the typo would slip through. But, of course, like pointed out above - it still can slip through quite easily... given how this only is performed on inline object literals.
// ^? | ||
``` | ||
|
||
By the way, the same behavior happens with `for ... in` loops: |
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.
funny thing is that TS doesn't complain about this at all (TS playground):
let key: keyof typeof yetiSeason
for (key in yetiSeason) {
console.log(yetiSeason[key]);
}
it is an intentional type hole 🤷♂️ I wouldn't recommend relying on this though
|
||
The only things in JavaScript that don't have properties are `null` and `undefined`. Attempting to access a property on either of these will result in a runtime error. So, they don't fit the definition of an object in TypeScript. | ||
|
||
When you consider this, the empty object type `{}` is a rather elegant solution to the problem of representing anything that isn't `null` or `undefined`. |
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.
Just something else that comes to my mind that might explain what it means that TS is a structural type system:
const hasName: { name: string } = () => {} // ok, functions have `.name` property with a string type
|
||
In this case, TypeScript shows an error when we try to pass the `Song` class itself to the `playSong` function. This is because `Song` is a class, and not an instance of the class. | ||
|
||
So, classes exists in both the type and value worlds, and represents an instance of the class when used as a type. |
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.
but u can get the type of a class with typeof Cls
if you truly care about the static side of the class
} | ||
``` | ||
|
||
Both point to the current instance of the class. |
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.
It could be helpful to mention what that means more.
With inheritance this
will refer to actual current instance of the class that might be derived from this declaring class:
class Song {
play(): typeof this {
return this;
}
}
class RapSong extends Song {}
const oneBeer = new RapSong()
oneBeer.play() // RapSong
}; | ||
``` | ||
|
||
This is because arrow functions can't inherit `this` from the scope where they're called. Instead, they inherit `this` from the scope where they're _defined_. This means they can only access `this` when defined inside classes. |
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.
No class in sight and I can utilize this
:
function makeObj() {
return {
counter: 0,
createInc() {
return () => {
this.counter++;
};
},
};
}
|
||
This might seem weird at first - surely these functions are under-specified? | ||
|
||
Let's break it down. The callback passed to `handlePlayer` will be called with three arguments. If the callback only accepts one or two arguments, this is fine! No runtime bugs will be caused by the callback ignoring the arguments. |
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.
No runtime bugs will be caused by the callback ignoring the arguments.
Until you pass it around 😉
const fn1: (arg: string) => void = (arg: string, arg2?: number) => {};
const fn2: (arg: string, arg2?: boolean) => void = fn1;
fn2("", true); // oops
} | ||
``` | ||
|
||
This approach avoids the whole issue with the keys, because `Object.values` will return an array of the values of the object. When this option is available, it's a nice way to avoid needing to deal with issue of loosely typed keys. |
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.
There is an interesting observation to be made about Object.values
here... if Object.keys
returns string[]
because the object could have more properties... why the hell this one returns (string | number)[]
?
const test2 = Object.values({ a: 1, b: '' })
The same could be said about Object.values
... the object could contain just any properties and just any values. So if TS is concerned about it... the return type here should really be unknown[]
.
No description provided.