Transcript
This transcript was autogenerated. To make changes, submit a PR.
Jamaica real
time feedback into the behavior of your distributed systems and
observing changes exceptions errors in real
time allows you to not only experiment with confidence,
but respond instantly to get things working again.
Close.
Let's start from some easy example
which is not documented in typescript
documentation, but I have found it
very useful. So currently you
all know that we have lowercase built in type
which will just lowercase your string.
For instance. Let's try it. Type test equals
lowercase.
For instance a so
test is equal lowercase
a so previously, if you provide lowercase
string, it will
do nothing. Because string is very general type,
it's hard to imagine how we can lowercase
any string previously. However,
after typescript 4.8 you
are able to use lowercase with regular
with common type with
general type string and you will get an
error if the string will be uppercased.
For instance, we have our string STR
variable which should be only lowercased.
Here we have okay, here we have an error because
of how it works. Now as
for second one also undocumented
feature. At least I didn't find in documentation explanation
of this behavior. However, you can find some
explanation here in this stack overflow answer and
in my article in
my blog undocumented features. You can find my blog here catchtscom
okay, so what about inference prioritization?
Just imagine that we have some props, select props
which expect value generic which
will be used for value and for options.
And then we have just, I don't know, any react
component select which will safer
provided value. So here you see I have provided
generic constraint that value should extend string
and our props will be select
props with this value.
Current constraint is that if options,
sorry, that value should be one of options.
So for instance, if options are red and
yellow, value accordingly should be either red or
yellow. So this is
okay. However, what about this?
It is obvious that this component
that these props are wrong because value should be either red
or yellow and in this case it is green. Why?
So currently it just should
work as it is because value is just an
inference of your props. So if you provided
a value green, then it is perfectly fine.
So as you might have noticed, value is
red or yellow or green.
If you want to make sure that value should be
only red or yellow, you should just add this small
sync boom. This means.
This means that prioritization of inference
of value is lower. It means that first of all we
will infer a value for options property and
only then we will infer for value.
So once we have inferred red and yellow for
options property typescript now
is aware that value should be either red or yellow.
Apart from that,
using intersection with empty object also
is interesting because it means
that it is non nullable value.
It was provided in I
don't remember typescript 4.8 or 4.9,
sorry, don't remember exactly, but they even
replaced non nullable built in utility
type with this with intersection
of generic ta and empty object. So now,
as you see, null is highlighted as
never, and if I will provide two it
is okay. For instance, ari it
is okay. So it
was very interesting for me to see that this small tree
can be used for both cases and furthermore,
least it, at least this behavior is not documented and
it is, I would say, very unknown
feature, but very useful.
So let's go to our next section.
Useful patterns how
to say I would say it is very popular
question on stack overflow this
example imagine that we have some object, I don't know,
some dictionary doesn't matter. And we
want to create a function which expects
this object and property which
corresponds, for instance to strings.
What I mean, I mean, if we have this object where a and
c properties are strings,
then our function should expect only
a or c properties, like we are
not allowed to provide b property. So here
we should get an error,
but this string property is not implemented yet,
so there is no inference here at all for now.
So let's create our utility
types to accomplish this task.
So first of all, I have created values this
utility type. It is very popular utility type
and it just returns us union
of all values. What does it mean? Type test
equals values type
of object oops.
So it is string and number because union of
all values. If I add it as const, I will
get a 42 and c. Yes,
just union of all values. Nothing complicated,
I would say. As for this
one, filter property by object
and type. This is also,
I would say, popular utility type. It iterates
through object and checks whether object
value extends some type. For instance,
in our case we are expecting here that
if object property extends
number, we will return this object property.
Otherwise we will get never.
So we just filtered object
properties by type.
For instance here
I get b only because I have provided a
number. If I will provide a string,
I will get a and c. So pretty straightforward.
More explanation you can find, as I said on my
blog. So let's
try to implement our function.
So here I have provided two generic
two generics for type inference. First, one is
object, which should be
a subtype of record where each property is allowed
object, key, string, number, symbol and value
is unknown because it doesn't matter for us which
value should have this object. And as for key,
it is like our most interesting
part. We are calling filter
property by I provided object and string.
It means that I want to filter object keys
by string. Now this
intersection is very important because without it
it will not work.
As you see, I have an error here. It says to
typescript that hey, this object
which is provided by you has
a key which corresponds
to string. This string and this
string should be like the same type.
It means that if I will call this
object with key, it allows me to use
any string prototype method charcoal add
includes index off.
So as you see, it works for outer world and
for inner sorry, not world but scope
for function outer scope. So you are allowed
here to provide only allowed key and for
inner scope of function. For instance,
if I provide here a number,
so we want a
number. C is not allowed anymore because
it should be only b because we have only one number property,
it is b, so it is allowed
us to use b. And here we are allowed to use
two exponential to fix to local string.
So all number prototype methods.
Okay, let's go to another
example. Believe me,
people are asking on stack overflow
once per week why Ri
prototype includes doesn't work with tuples,
I mean with immutable arras I
will not provide you with explanation why you can
easily find it. It is very popular.
I want to provide you with solution which
without type assertion, I mean without
using s assertion.
So currently if for instance you have some immutable
array and you want to check
whether it is includes d, you will get an
error. So how we can
fix it? How we can write our code more safer
we can carry it so we can make a
function which returns a function.
So what does it mean? I have created a
function with tuple. I have provided
generic parameter list which extends
an array of strings. And here I have just
provided this type. I have used this
rest operator for type inference.
So currently nothing complicated.
As for the second one function, second one function which is
returned function by this remember here
we have two functions, is a type guard,
what does it mean? It means that it
expects us one argument which is prop
sorry, which is string, and if it returns
true, it means that prop is a part
of the list, is the element from the list.
Just want to remind you what this syntax
means. If we have a
tuple type with square bracket notation where we
just use general type number, it will
return us a union of all type elements.
What I mean, I will just provide you with small example, just to
have an overview what we are doing here.
So type here
and one, two. So this is
our type. Our type is tuple, which consists
of three elements, one, two, three.
If I use square bracket notation and we'll type
a number, it will return us a union of
all elements. Okay,
so now when we understand what's going
on here and here, we can use our utility
function. First of all, I have created includes
function. Please be aware that includes is
a function, because vistauple returns us a
function. It means that includes
checks whether prop is one
of tuple elements, in our case abc.
So here you were not allowed
to use like any other string except
abc. Here we are allowed to use any string
because prop is actually a string. So it might be
any string. And here, after we apply
if condition, sorry, condition statement
str will be a, c and b.
Okay, let's proceed. Let's go
to our next section. It is safer typescript.
It is very easy
sync and you need to be aware that using
interfaces in typescript is
more safer than using types. There are
a lot of discussion you can find in Internet that I
prefer to use types, I prefer to use interface.
Somebody prefer to use anything else? I would
say in typescript it is not a matter
of preference whether you want to use interface or
type. It's a matter of, I would say
safety or what
I want to say it shouldn't be a preference. It should be a
rule that if you have some object with some predefined
properties, like here we have an interface animal tag
name, we can use generic type here.
In this case, it's important that
we are using an interface and not type. We should use
type for aliases, for some
making fun utility types,
some iteration, but not for declaring just
a shape of object.
Why? Just imagine that you have
a type animal
and you have some function handle record,
for instance, which expect us an object
where key is a string and value is unknown and
this object will be mutated. So imagine
you have some generic function which mutates your object.
I don't want to say that mutating objects in typescript
is very cool idea. However we might
have it. So if you have this function and
it is perfectly fine that you are mutating object here
you can assign any value like 23 or,
I don't know, some array of numbers and it is
okay, typescript perfectly fine with it.
Furthermore, you have an object
which is an animal, and you are passing this object
to this function and this object
will be mutated. Just ask
yourself, is it okay that I can mutate
any object in my application.
For instance, okay, I know you are not mutating your object,
but maybe your colleague mutating it.
Or maybe when you leave your project and one
year later somebody will
join your team who likes mutating objects,
and they will probably mutate it and
it is not okay, that animal is mutated.
So if you use here
an interface,
typescript will disallow you mutating of this
object. Because when you write interface, you write
sealed. Okay, sealed.
It is not typescript keyword, but typescript
treats it in this way that this
object can be extended, can't be changed.
It is like interface.
I would say this is very important because I
have seen a lot of examples with mutating when people are using
types, and believe me, it leads to some runtime
errors or production bugs. So it
is very easy to remember and very important
to use.
And what's also important,
why this happens? Because interfaces are not indexed
by the default. This is the difference between
interfaces and types. Types are indexed, interfaces are
not. Okay,
let's proceed with our next section. It is recursive
data structures.
In previous typescript versions,
you were not allowed to compute typescript
range of numbers. You were allowed to create
range for four t elements,
something like that. Currently,
after optimization of tail recursion
in a typescript, you are allowed to create up to
1000 numbers.
It should be. Let's try nine.
Nine, nine,
small improvement.
So as you see, you have a number range of
100 digits. Why 100?
Because we start from zero.
Sorry, no,
100, but almost 1000.
Okay, this was just for testing. I will explain
how it works.
Sorry. Here we have a recursive utility
type which expects us two arguments. One n corresponds
to our count of numbers, and second
one is result like our
array of numbers.
When we call it, we are checking whether our lens
of our result length of this tuple extends
our expected lens to be created.
If yes, we will just return a union of all elements in
array. Otherwise we will run compute
digit range this utility type with same argument
n and with extended
result. So every time we will add to
result results length. So it
starts from empty array.
Imagine that n is five. Let's start our
first iteration. Result length is zero,
extends n false. We will go here,
and here we will add result,
it is empty array, it will just get
rid of it. And second, one result length is zero.
However, it will be an array
of one element. So next iteration will
be result length will be one. This is how
we increment. Okay,
also very popular question, how to
create repeated string which
matches some pattern. For instance,
we are not allowed to use reg x patterns in
typescript template literals,
however, and also we are not allowed to
create some infinity string type
with some repeated pattern.
It would be nice to have it in typescript, but currently we
don't have it.
How we can make it works.
For instance, we have some type coordinates.
This type corresponds to number which are separated
by comma, and the coordinate string
is separated but by semicolon. I mean, this is
our example,
this is our type of coordinates. But what
if I want to make it longer?
You will get an and here, for instance, I expect
this we are getting an error
because it's obvious what
we can do. We can recursively, we can
apply this utility type like apply this algorithm.
I don't know what is better to say algorithm or
pattern. And we can recursively
create up to 50 or
100, I don't remember exactly up to 100
unions of different sizes
of coordinates. What I mean please.
Here I have created compute coordinates with ten iterations,
sorry, no iterations, but with ten elements.
So this type allows
me to use coordinate where I have only two numbers,
then when I have four numbers, then when I have
six numbers. So as you might have noticed,
I don't have three numbers, five numbers, because it doesn't
make sense because our coordinates should
be atomic, so it should
consist of two numbers which are separated by comma.
How does it work? Works very similar,
but it only differs how
I compute the value which is pushed to our
tuple. Here I'm using concat previous
it just types last element of my
result and concatenates it with new
coordinates. So this is how each iteration coordinates
made one coordinate more.
Here you can find links to
articles with more explanation because I understand that it
might be hard to
understand how it works only from this video.
For instance, I prefer reading articles
rather than watching video, but some people may prefer
watch video. Okay, so once
we have finished with this pattern, I believe we
can proceed to hex validation.
I will show you what I will do at the beginning,
so you will be aware what I am talking about.
I'm talking about a function which expects us
some object where key is a caller
name, but in this case it doesn't matter whether it is color name
or not. No,
and value should be a valid hex
value. Valid hex value can
consist of either three chars or
six chars.
Furthermore, these charts should
match some pattern. Because you are not allowed to provide just
six x, it will
be an error. So let's
start typing this function step by step.
So first of all, I want to compute a range
of numbers from zero to nine,
which are this number. Please keep in mind that these
numbers are only allowed in
our hacks, but it also all number which you can
use. You are not allowed to use more than that.
So currently, as you might have noticed, I just wrapped
this range in a template literal string,
so each number is a string. Also,
I'm allowed to use charts from a to f ABCDef,
only six charts and they might be
lowercased or uppercase it. So I just used
the built in utility type I uppercase.
So I have a union of my charts, lowercase it
and uppercase it. And this is
my final hex value.
I mean not hex value, I mean all charts
or all digits which are allowed to use
in our hex string. It is from zero to nine
and from a to f, uppercase and lowercase.
So imagine that you
need to write a function in Javascript
which will validate it.
Please forget for a moment. For bit validation
for some bit manipulations, let's just stick
with raw string. So currently,
since our hex value can
be either three of six elements,
what we need, we need to check whether string is
whether string lens is allowed.
In order to do that, we need a utility
type which will return us
a string glance. This is
why I have created this simple utility type
recursive utility type which iterates
through each char and every time
adds this chart to
accumulator. So at the end we have a
tuple which lens
is equal to string lens. So in our
case this will be three or
four. Okay,
the next step we should validate whether
it is three or six.
Let's write very
simple validate lens utility type which expects
us our string and our expected lens.
And here we are checking whether string lens of provided strings
extends lens. We will return string.
Please keep in mind that always when in this case
when it is OC,
we are returning string. Not truer or false,
just string. It is important to
be in mind, otherwise we are returning never.
So here if string is ABC
and expected lens is two, we will get a never.
And here we are getting ABC because expected lens is
three and we have three charts.
Okay, I hope it's easy to understand.
Let's proceed. Here we have validate
hacks what this utility types
do. It iterates through string
like through each char.
Here how we are getting each
char, and here we have a rest charge. So every time we pick
one char and then
we are checking whether this char extends
our hex value extends our allowed hex value
from zero to nine and from a to f,
uppercase it and lowercase if yes,
we will add this
allowed char to our cache here.
Otherwise it will be never.
So here you can check how it works.
ABC each char is allowed
so it returns are ABC.
Second one, HR is not allowed because it's
just not allowed. Okay,
what are we doing next? It is very important to
understand how it works because currently what we are doing,
we are just using all our
validators and we are making
intersection of them. Why we are making intersection because
each validator returns us
either string, provided string
or never. So in case if it returns
string like provided string
and intersection of equal strings will
just produce this string.
Otherwise it will produce never.
So you can have, I don't know,
20 validators where each validator returns
you a string, either string or never, and just apply these
validators like that.
Validate something else.
It will be easy to read, easy to understand, no more
cryptic types. So here imagine
you don't know typescript validate hex validate
lens. At least we are aware that here
we are doing hex validation and lens validation.
Here I am using union because string lens
might be six or three.
Second one is just adding this fancy
hash symbol to each property,
and then this is how this function works.
I use type inference or on function arguments.
So I have created three generics.
One generic for key object key,
second one for object value, and third one is
our color map. So color map
should correspond to this object.
And this is wrapped into my validator
and this is how it works.
So here you see that if I
use invalid hex value,
it will return me an error. Otherwise it works
as expected. So one
more thing that we need to use
extra function for type inference.
We are not allowed to use just alone object.
We need to use this function despite the
fact that this function do nothing. So it just returns
us dictionary.
That's it. Thank you
very much for your attention. See ya,