Home > OS >  Typecheck before loop ignored inside loop?
Typecheck before loop ignored inside loop?

Time:10-04

In a React component I have something similar to:

  if (props.someArray){  // typecheck because someArray is of type 'things[] | undefined'
    props.someArray.forEach((element, i) => {      
        someFunction(i, element, props.someArray); // error despite typecheck (due to "| undefined")
    });    
  }

  // The functions someArray argument's type is only 'things[]', not 'things[] | undefined'
  const someFunction = (i: number, element: thing, someArray: things[]) => {
     someArray[i] = // do some stuff;
  }

Since the typecheck is already done before the loop, is it really necessary to do it again before calling the function inside the loop? (inconveniently checking on every iteration instead of just once before looping).

  if (props.someArray){  // <- I'd like to keep just this
        props.someArray.forEach((element) => {    
            if (props.someArray) { // <- is this really necessary?
                someFunction(element, props.someArray);
            }
        });    
      }

CodePudding user response:

  • Array.prototype.forEach uses a closure (i.e. an entirely separate function) for the loop body, and TypeScript is cautious about how type-information passes between function boundaries.

    • Namely because TypeScript has no way of knowing how forEach will invoke the callback because any script can simply overwrite or replace the default Array.prototype.forEach implementation and do something silly, like repeatedly invoke the function on only the first element or something.
  • If you use for(of) instead then you won't have any problems because it doesn't use a closure: as it's a native language construct the TypeScript type engine (is that a word?) can reason about it more than it can with .forEach.

  • Other reasons to use for(of) instead of forEach:

    • You can use await inside for(of).
    • You can use break to abort a loop (you can't abort a forEach without using throw).
    • It works with any iterable, whereas forEach is only available on certain specific collection types (Array, NodeList, and a few others).

So change your code to this:

if ( props.someArray ) {

    for( const el of props.someArray ) {

        someFunction( el, props.someArray );
    }
}

If you want to get both the array-element and its index inside the for loop then use entries() like so:

if ( props.someArray ) {

    for( const [idx, el] of props.someArray.entries() ) {

        someFunction( idx, el, props.someArray );
    }
}
  • Related