I'm very new to typescript and I'm trying to learn the language.
I'm reading through a component and I see this:
interface FilterRowProps {
cannotRemove: boolean
filterName?: string
}
const field = getField(demo.fields)(filterName!)
Questions:
This made me think, What is ! in typescript signified for?
What happens if it's not passed (optional parameter?) does it throw an error or does it not do anything?
Does it make sense to make argument optional and use ! inside component?
Since filterName props is optional, what does filterName! signify?
Can someone explain? Super confused.
The ! is the non-null assertion operator. It tells typescript "i know this looks like it might be null/undefined, but trust me, it's not". This is occasionally needed in cases where typescript can't figure out that your code eliminates the possibility of a null or undefined.
But be aware that like any type assertion, you are telling typescript not to check your work. If you use it, and it actually can be null/undefined, typescript will not alert you to this fact, and you may get an error at runtime.
In short: you should rarely use this. Most of the time, if the types lead typescript to deduce that it might be undefined, then it's probably right. You should then write code to deal with the undefined.
Related
At work, I'm involved in maintaining our React component library, which we're very slowly in the process of converting to TypeScript. (But my question is about Typescript more generally and not specific to React - that's just how the question has arisen and how I imagine it might arise for many others!)
Like many such component libraries, we often use a union of string literals as the type for some props, to ensure they always have one of a small number of approved values - a typical example might be type Size = "small" | "medium" | "large";. And while we don't always use a type alias for them as shown there, it is frequent enough that we do, as the type may need to be referred to in a few different places, and it's nice (particularly with larger unions than this) to not have to type out the same thing all the time, as well as knowing that if the design team ever want us to add a new extra-large size or whatever, the type at least will only have to be updated in one place.
This works fine of course, but we've discovered that in terms of intellisense it leaves quite a lot to be desired. We want our consumers, who likely don't know all the valid values off my heart, to be told what these are by their IDE when rendering the component. But by doing this in the most obvious way - ie like this:
type Size = "small" | "medium" | "large";
interface Props {
size: Size;
}
then when consuming the props in any form, IDEs such as VSCode on hovering over the size prop will simply display the rather unhelpful Size as the prop's type, rather than the explicit union of 3 strings which would be far more helpful.
(Note that although the above is using an interface rather than a type alias, which is the way we've decided to go after some debate, the same issue is present when using type for
the props type.)
This seems like it should be a common question, but many searches on google and Stack Overflow have failed to turn anything up that's specific to simple unions of strings.
There are discussions about ways to get TS to "expand" or "simplify" complex types - like this Stack Overflow question and its great answers, or this article, which basically show the same solution although they differ in details. But these seem to be aimed at - and certainly work on - object types specifically. Sadly, no matter which of these transformations is applied to the type of the size prop in the above example, TypeScript still stubbornly shows the unhelpful Size as the prop's type when actually consuming it. (For those for whom this makes sense - Haskell is among my favourite languages - I would phrase this more succinctly as: these solutions appear to work on product types but not on sum types.)
This is demonstrated in this TS playground example - specifically the size2 prop. (This shows only one form of the Expand type, but I've tried every slight variation I've either found online or have come up with myself, with no success.)
The others - size3 and size4 - are attempts at using template literal types based on the same "trick" that is behind the Expand example. I understand they're based on using a conditional type to force distribution of the operation across a union and then making sure the operation is essentially a no-op, so the actual type is still the same but after hopefully forcing TS to compute something across the union to output a "new", "plain" union. Since the Expand type suggested above iterates across keys, fine for an object type with properties but unclear if it has any meaning for a union of string literals, it seemed using a template literal as the operation in this trick was the same idea but adapted to such a union of literal string types. And specifically, concatenating with an empty string is the only obvious way to make this keep the strings as they were.
That is, I thought
type NoOpTemplate<T> = T extends string ? `${T}${""}` : T;
might work. But as you will see from the playground link above (size3 prop), it doesn't.
I even hit upon the idea of using a generic type with two parameters:
type TemplateWithTwoParams<T, U extends string> = T extends string ? `${T}${U}` : T;
This "works" in a sense, because the prop defined as notQuiteSize: TemplateWithTwoParams<Size, "x">; displays as an explicit union as desired: "smallx" | "mediumx" | "largex". So surely supplying an empty string as the U parameter will do what we want here - keep the strings in the union the same, while forcing explicit display of the options?
Well no, it doesn't, as you can see from the size4 prop in the example! It seems the TS compiler is just too clever, as all I can assume is that it spots that in this case we're just concatenating with an empty string, which is a no-op, and therefore doesn't need to actually compute anything and thus outputs the same type T as it was given - even via the type alias.
I'm out of ideas now, and surprised this doesn't seem to be a common problem with clever solutions like the above Expand that I can read about online. So I'm asking about it now! How can we force TS to display a union type as an explicit union, when used as part of an object or interface, while still keeping the convenience of using an alias for it?
It turns out that I was thinking along the right lines, I just needed a different "do-nothing" operation to apply to each member of the union.
Specifically, wrapping the value in an object, and then extracting it, like this:
type WrapInObject<T> = T extends any ? { key: T } : never;
type Unwrap<T> = T extends { key: any } ? T["key"] : never;
type ExplicitUnion<T> = Unwrap<WrapInObject<T>>;
works as intended. Here's the TS playground example from the question expanded with this version - as prop sizeThatWorks, where if you hover to see the intellisense you will see the desired output of the explicit union, rather than the Size alias.
This has a further advantage of presumably working for any union, not just one of string literals. I wish I knew this would continue to work in future versions of the TS compiler - I hate to think that future versions may be "clever" enough to realise that this wrapping-and-unwrapping is a no-op, as apparently happened with my attempts using template literal types. But this seems to be the best available, at least at present.
Can you help me understand the following compiler error?
When I initialize the model to either undefined or [] in that code, the error goes away. When I leave the model implicitly undefined, like in the screenshot, I get an error.
It doesn't complain about somethingPotentiallyUndefined.
It complains about somethingPotentiallyNumber.
TypeScript disallows to invoke Array.from with a number, as in
Array.from(42)
In your case, model can be GridInputSelectionModel, which can be GridRowId, which in turn can be number, which is bad.
The error goes away if you explicitly set model to undefined, because the flow sensitive type inference can figure out that at that specific location model can be only undefined, so that the argument to Array.from is guaranteed to become [], which, unlike a number, is valid.
The problem is that GridInputSelectionModel is potentially not iterable. If you don't assing "undefined" TypeScript does not know which type model has (both would have to be iterable). Is it GridInputSelectionModel or undefined?
By assigning undefined you answer the question.
Because undefined || [] (which you explicitly stated) is []. The error goes away because "[]" is iterable. However, this has nothing to do with your model variable anymore you would always pass an empty array.
I come from object oriented languages such as Java and I'm trying to convert a .jsx module to a .tsx to make use of types.
I have to say it is pretty easy and straightforward but I don't like the following:
let methodName = 'GET';
This is a variable declaration in a method of mine.
The compiler is fine with me not assigning a type, but coming from java I don't like that! I'd like to get an error "type of variable methodName is undefined" so that I'm forced to write it in this way:
let methodName:string = 'GET';
The same goes for my methods, for typescript the following is absolutely fine:
handleButtonClick = evt => {
}
but it's not fine at all, I should be forced to do something like this:
handleButtonClick = (evt:React.MouseEvent):void => {
}
Is it possible to achieve this by setting some option?
In the compiler options section of the documentation, you'll find the --noImplicitAny option that tells TypeScript not to allow implicit any.
Note, though, that your first example doesn't make mehodName implicitly any:
let methodName = 'GET';
TypeScript infers the type of methodName from what you're assigning it, and trying to assign something other than a string will be an error. The type inference documentation goes into this in detail. A lot of the time, you don't need to explicitly type things, because the type can be inferred. The code is still typesafe.
This would implicitly make methodName of type any:
export let methodName;
With --noImplicitAny, that's an error. Without it, the implicit type any is used for the exported variable.
I am new to TypeScript and am porting a reactjs project. A few errors popped up with the styles, including the tableLayout, borderCollapse and userSelect properties.
Doing some Googling yielded an explanation for this type of error:
https://github.com/Microsoft/TypeScript/issues/18744
https://github.com/Microsoft/TypeScript/issues/11465
and I was able to get rid of the errors with type coercion eg
const someStyle = {
...
userSelect: 'none' as 'none',
...
}
It seems #types/react uses frenic/CSSType. Do these errors represent omissions in their typed styles, or am I doing something wrong? Should they be reported there?
From what I can tell, it's not a result of either omissions nor something you've done. You could even say it's because the types are too specific, but really this is a limitation of typescript itself.
As the first issue mentions, it's an issue with what type is given to an object literal when it's declared (specifically, that object fields holding a string are given the type string and not their literal type).
This isn't something that's likely to ever change, given that there is already a not-too-awful workaround (the type cast you demonstrate). Moreover, from that issue (near the bottom):
we have no plans on changing the design at this point. we already had
multiple iterations on how literal types are inferred and the current
design seems to be the best compromise we can reach.
So ultimately, this is something we have to live with. ¯\_(ツ)_/¯
I have an angular 1.4 project on typescript, the project is getting bigger and bigger and our team is really tired of interfaces that we have declared (so that all objects are typed, in comparison to any)
I'm asking if this is a good idea or not? I chose typescript because I wanted to have a typed project, should I drop the interfaces or not?
You can get by without using interfaces and going with any, but in the long term you are probably going to regret it. If your team is sick of the interfaces you've created, I would put time in fixing those instead of abandoning them. There is a significant argument around if typed languages reduce the number of errors found in code. Personally, I think they do.
What I've found with typed languages is that it helps remove stupid mistakes we all make, and this clears up our time to focus on actual logic problems in code. Not everyone agrees with me on this, but I will always pick a type language over a non typed one, especially if the team is used to dealing with languages like Java or C#.
Interfaces can be very helpful if used properly.
If you are just doing this....
IFoo { ... }
Foo implements IFoo { ... }
Then they will not help as much as they do in other typed languages (C#/Java). Because the type checking in TypeScript is dependent upon the properties on the object and NOT on the declared type. This is because you can simply write this...
MyCtrl (foo: Foo) { ... }
//instead of
MyCtrl {foo: IFoo) { ... }
This will not hinder unit testing in any way since as stated above the type checking is based upon properties and not declarations.
There are cases when interfaces can be quite helpful, for instance a common use case is when defining an object as a parameter...
doSomething (options: ISomethingOptions) { ... }
There is no harm in creating interfaces for everything, you just need to determine what level of typing works best for your team.