Search code examples
arraystypescriptinterfaceprototype

How to properly extend Array prototype in Typescript 4.0.3?


I'm trying to extend the base Array interface with some custom methods. I looked around SO and typescript docs and finally put together the following code:

// In module Func.ts

declare global {
  type Array<T> = {
    intersperse(mkT: (ix: number) => T): T[];
  };
}

if (!('intersperse' in Array.prototype)) {
  Array.prototype.intersperse = function intersperse<T>(this: T[], mkT: (ix: number) => T): T[] {
    return this.reduce((acc: T[], d, ix) => [...acc, mkT(ix), d], []).slice(1);
  };
}

However, I'm getting the following errors:

// On type Array<T> = { ... }
Duplicate identifier 'Array'.ts(2300)

// On Array.prototype.intersperse = ...
Property 'intersperse' does not exist on type 'any[]'.ts(2339)

Also, whenever I try to use intersperse in some other file, I get the error

Property 'intersperse' does not exist on type 'Element[]'.ts(2339)

Which is to be expected, considering the declaration in Func.ts seemingly didn't work. From this I gather that the SO questions are outdated (or incomplete) and that something has changed since.

So, what's the best way to extend to get rid of these errors and extend the Array prototype? Before you say I'm not supposed to do so — yeah, I know all the risks, and I made an informed decision to do it anyway.


Solution

  • Array is defined as an interface not a type. Interfaces in typescript are open ended and can be added to by multiple declarations. Types do not share the same feature.

    export{}
    declare global {
      interface Array<T>  {
        intersperse(mkT: (ix: number) => T): T[];
      }
    }
    
    if (!Array.prototype.intersperse) {
      Array.prototype.intersperse = function intersperse<T>(this: T[], mkT: (ix: number) => T): T[] {
        return this.reduce((acc: T[], d, ix) => [...acc, mkT(ix), d], []).slice(1);
      };
    }
    

    Playground Link

    As T.J. Crowder mentioned you might consider using Object.defineProperty to ensure the property is not enumerable:

    export {}
    declare global {
      interface Array<T>  {
        intersperse(mkT: (ix: number) => T): T[];
      }
    }
    
    if (!Array.prototype.intersperse) {
      Object.defineProperty(Array.prototype, 'intersperse', {
        enumerable: false, 
        writable: false, 
        configurable: false, 
        value: function intersperse<T>(this: T[], mkT: (ix: number) => T): T[] {
          return this.reduce((acc: T[], d, ix) => [...acc, mkT(ix), d], []).slice(1);
        }
      });
    }
    
    

    Playground Link