TypeScript 3.7 Beta
devblogs.microsoft.com
devblogs.microsoft.com
They understand very well how bad it would be if the two languages were to diverge.
type Direction = "up" | "down";Enums are transpired to objects so you can iterate the keys/values. You can't do that with SL which are a transpiler feature only.
Also you can't codedoc string literal values like you can with enums where it shows up with intellisense.
I tend to prefer enums as a result
const types = ['x', 'y', 'z'] as const;
const a: typeof types[number] = 'a'; // not ok
const b: typeof types[number] = 'z'; // ok const enum Directions { Up, Down, Left, Right }
console.log(Directions.Left)
// emits js file with console.log(2)
https://www.typescriptlang.org/docs/handbook/enums.html#cons...By using only union types as "enums" and never using enums or namespaces, I guarantee that all the TS-specific code I write can be erased and if I ever were to use just JavaScript in the future, I could.
Also, I use Babel to transpile TS to ES3.
const test(direction: Direction) => direction;
test('up' as 'up');
But now that I'm testing in typescript playground, I no longer need to write the "as" part — simply `test('up');` works fine as well.I wonder if you've seen this quirk of typescript before.
It's when you do
const dir = "up"; // TS infers type "string"
test(dir); // error
For mutable variables (let/var), you usually want TS to infer `string` in the first line. IMO the fact that they also infer `string` for constants is a design flaw, that I hope will one day be fixed.They recently added a feature to make this less painful:
const dir = "up" as const;
test(dir); // works
That's the same as `as "up"` bit without having to duplicate the string.(example in typescript playground: https://www.typescriptlang.org/play/#code/C4TwDgpgBAIglgJwgY...)
0: https://github.com/Microsoft/TypeScript/issues/14833
1: https://github.com/Microsoft/TypeScript/issues/14833#issueco...
``` function assert(condition: boolean, message: string) { if (!condition) { throw new Error(message); } }
assert(x !== null, "x can't be null");
// Flow understands that x can't be null here because `invariant` throws an error otherwise, but TS doesn't ```
However, TypeScript couldn't understand that `x` can't be null after that call since it doesn't understand what `assert` does. For that reason, we had to write a codemod that replaced all of our `assert` calls with an if condition that threw an error inline.
The new `asserts` syntax is nice (it would be even better if TS could do this automatically like Flow though), and will be a much welcome update for us.
What are they good for? Maybe there is a use case I am not getting, but this
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg)
}
}
Seems like the equivalent to function yell(str : String) {
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
But the latter has much less code and is clearer too.What am I missing?
Why should the type system have anything to do with something like this?
function something(num : number) {
assertIsInRange(num);
return num + 1;
}
function assertIsInRange(num: number, msg?: string): asserts num {
if (num < 50 || num > 100) {
throw new Error('not in range');
}
}
something(50);
How is `asserts num` helping here?So If I wanted to do that I would have to
if (typeof num === 'number') {
assertIsInRange(num);
}
anyway.I think I still don't get it.
The fact that it’s a built in feature of the language also can sometimes helps the compiler infer what’s the properties of code after it. But theoretically , yes you could very well code some equivalent testing function yourself, except for the compiler inference part it would probably be equivalent.
Not in TS. This code
function something(num : number) {
assertIsInRange(num);
return num + 1;
}
function assertIsInRange(num: number, msg?: string): asserts num {
if (num < 50 || num > 100) {
throw new Error('not in range');
}
}
something(50);
gets compiled to function something(num) {
assertIsInRange(num);
return num + 1;
}
function assertIsInRange(num, msg) {
if (num < 50 || num > 100) {
throw new Error('not in range');
}
}
something(50); function assertIsString(x: number | string): asserts x is string {
}
and TS will just ignore the fact that you didn’t check anything. One shouldn’t have to introduce a giant hole in the type system just to be able to encapsulate checks that TS should be capable of understanding.I filed this as a bug to see if the developers agree: https://github.com/microsoft/TypeScript/issues/33743
function test(x: number | string): void {
x.toUpperCase();
}
is an error and function test(x: number | string): void {
if (typeof x === "number")
throw new Error();
x.toUpperCase();
}
is not. So it should just as easily be able to understand that function assertIsString(x: number | string): asserts x is string {
}
is an error and function assertIsString(x: number | string): asserts x is string {
if (typeof x === "number")
throw new Error();
}
is not. if (typeof x === "number")
throw new Error();
into a function.It doesn't infer argument types, but it does infer return types.
So yeah, TypeScript doesn’t but perhaps should infer assertion signatures where a return type is not specified. However, it would be bad if people who do want to specify the return type were unable to do so, so an `asserts` annotation syntax would still need to be available.
That would imply that the same function signature could produce different type checking results at the call site. That is something I find extremely frustrating as you can no longer reason about type checking locally.
Wow, that took a while! For already about 9 years this feature is in Coffeescript. And indeed it must be the star of the show because once you're used to that you almost can't live without it. Why did JS/ESxx never copied that idea?
edit: just read it has just been added to JS some days ago as well..
Changes to proposed features can still be made at stage 3 if critical issues come up. Otherwise, once implementors have sufficient experience with them (and tests & multiple implementations exist) the proposals will be eligible to move to stage 4, after which they'll be able to be included as part of the next ECMAScript spec.
Both proposals are progressing through the process very smoothly, but I wouldn't rely on them for anything critical just yet.
In this case, members of the TS team were actually championing these ES proposals :)
All of the above need method chaining, though:
The problem might be that we have most likely not yet handled the edge case situation where a.b and a.b.c doesn't exist. And just want to log out the value, and print undefined if it doesn't exist.
Ruby has had this kind of syntax since forever and it's one of the things that people love about it.
> The problem might be that we have most likely not yet handled the edge case situation where a.b and a.b.c doesn't exist
That's not an edge case, that's an expected case. If it doesn't exist, nothing is supposed to happen.
OOP just obfuscates this basic fact of programming life to the point where it's not obvious anymore. That is not a good thing, it's a huge waste of time. Favor plain data over "objects" and functions over methods. Favor plain SQL queries over ORM.
Depending on the language, `a.b && ...` could be checking either or both of the following:
- does `a` have key 'b'? - is `a.b` truthy?