#Style q: Should I prefer utility types when I could directly set the type instead?

36 messages · Page 1 of 1 (latest)

azure chasmBOT
#
sleepeasysoftware#0

Preview:```ts
function lineNumberByIndex(index: number, s: string) {
const re = /^[\S\s]/gm
let line = 0
let match // how would you write this type?
/*

  1. let match: RegExpExecArray | null;
  2. let match: ReturnType<RegExp['exec']>;
    */
    let lastRowIndex = 0
    ...```
hollow zenith
#

!ts

azure chasmBOT
#
function lineNumberByIndex(index: number, s: string) {
  const re = /^[\S\s]/gm;
  let line = 0
  let match; // how would you write this type? 
  /* 
1. let match: RegExpExecArray | null;
2. let match: ReturnType<RegExp['exec']>;
  */
  let lastRowIndex = 0;
  while ((match = re.exec(s))) { // match is assigned here
    if (match.index > index) {
      break;
    }
    lastRowIndex = match.index;
    line++;
  }
  return [Math.max(line - 1, 0), lastRowIndex];
}```
hollow zenith
#

Hopefully my question is encompassed in these comments. Let me know if it's unclear

digital bough
#

Here's how I do it personally
For simple types such as the example you provided, I would just use the type itself

For more complex types, such as if the return type is a bunch of unions
Or if the library does not export the type for some reason (quite unlikely to happen) then I would use ReturnType

#

Oh also, if I am defining a function and annotating its return type
But I also want to annotate the type on the returned variable, then again I'd use ReturnType

#
function test(): {a: string} {
  const value: ReturnType<typeof test>= {a: 'abc'};

  return value;
}
hollow zenith
#

if I am defining a function and annotating its return type
But I also want to annotate the type on the returned variable, then again I'd use ReturnType
The way this is worded makes it sound like you would use ReturnType instead of : {a: string}, but you're not. I'm confused by that

#

But I get what you're doing there, I think

digital bough
#

Ah sorry
I mean, on the function return type I would provide the type as it is
But inside the body, for the value, i would use ReturnType

#

(But then again, I could give that type a name and use it everywhere)

hollow zenith
#

But isn't that redundant since the return will have to have the : {a: string} type?

#

I don't understand the advantage of simply doing this instead:

function test(): {a: string} {
  const value = {a: 'abc'};

  return value;
}
digital bough
#

True, and you could return the object directly too instead of through that variable
But what if the logic required the value to be declared using let and assigned through a series of if-else branches

#

Having the type annotation would show you which assignment caused the type to fail
Without it, the error would be on the function return type, and wouldn't show exactly where it failed

#

Let me create an example

hollow zenith
#

oh I get it

#

To me that sounds like a workaround for a compiler limitation. Would you disagree?

azure chasmBOT
#
mishall8399#0

Preview:```ts
function test1(): {a: string; b: number} {
let value

const randomValue = Math.random()

if (randomValue < 0.25) {
value = {
a: "abc",
b: 123,
}
} else if (randomValue < 0.5) {
value = {
a: "def",
b: "456",
...```

hollow zenith
#

Either way, that's a really good trick. I had no idea it had this effect

digital bough
hollow zenith
#

So if, in a certain context, I want to build the object in steps, adding a type to value would get in the way, right?

#

of course I could get the best of both worlds by assigning to a and b in steps, then return value as { a, b }?

#

But that seems like it would be really weird when I could just return it as an object literal

#

So maybe it's not the best of both worlds

digital bough
#

If a and b need to be figured out individually, then yeah you could do that and finally return the object { a, b }
But even then it would be the same issue of the error being shown at the return statement instead of wrongful assignment
Unless you add the type annotation to both like let a: string and let b: number

hollow zenith
#

I think that's usually what I do

#

Is there a big disadvantage I'm not seeing?

digital bough
#

Disadvantage to what?

#

Oh also, to your original use case with regex match
You don't really need the type annotaion for match
TS would do a "rolling type" meaning it updates the type as it goes on assigning new values
But disclaimer, it breaks in weird scenarios

azure chasmBOT
#
mishall8399#0

Preview:```ts
function lineNumberByIndex(index: number, s: string) {
const re = /^[\S\s]/gm
let line = 0
let match // how would you write this type?
/*

  1. let match: RegExpExecArray | null;
  2. let match: ReturnType<RegExp['exec']>;
    */
    let lastRowIndex = 0
    wh
    ...```
hollow zenith
#

to that last idea compared to defining value at the top

digital bough
#

Plus, no intellisense on the property names or values (in case of string unions)

#

And if you have type annotation, TS also helps to ensure that you are definitely assigning the value in all possible branches before returning it
If you miss that last else, you will be shown an error
Because if it doesn't hit any of the ifs then value remains undefined

hollow zenith
#

@digital bough thanks!