microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.62k stars 12.44k forks source link

make possible to extend class dynamicly via namespace or interface #37083

Closed bartekleon closed 2 months ago

bartekleon commented 4 years ago

Search Terms

static, methods, class, dynamically,

Suggestion

It would be really nice to be able to extend class types via namespace or interface, without creating a new class:

class newClass extends oldClass - not like this

declare module './oldClass' {
  interface oldClass {
    map(value: string): string;
    static map(value: string): string;
  }
}
declare module './oldClass' {
  namespace oldClass {
    static function map(value: string): string;
    function map(value: string): string;
  }
}

Use Cases

extending classes like this can make creating and adding plugins much easier. You don't need to take another class to add one function. (it becomes problematic when you want to add more than 1 plugin)

Examples

declare module './oldClass' {
  interface oldClass {
    map(value: string): string;
    static map(value: string): string;
  }
}

oldClass.map = function(f) {
  return f;
};

oldClass.prototype.map = function(f) {
  return f;
};

Current solution

declare module './oldClass' {
  namespace oldClass { // allows adding static methods
    function map(value: string): string;
  }
  interface oldClass { // allows adding methods to prototype
    map(value: string): string;
  }
}

oldClass.map = function(f) {
  return f;
};

oldClass.prototype.map = function(f) {
  return f;
};

Checklist

My suggestion meets these guidelines:

Although it might look like a duplicate (and some parts are duplicating) - there is no actual solution for extending static methods of a class, and this issue proves, that some workaround is actually ridiculous.

RyanCavanaugh commented 4 years ago

I can't tell what you're trying to do or what the deficits of the current options are. It's a bit confusing because extends means both "inherit" and "augment" and it's not clear which you mean

bartekleon commented 4 years ago

@RyanCavanaugh sorry for making it unclear. I want to add new methods (normal and static ones) to a class, but I don't want to use extends.

So this is what I want to do:

oldClass.prototype.myNewMethod = someFunction
oldClass.myNewStaticMethod = someFunction

What I don't want to:

class newClass extends oldClass {
   myNewMethod(){}
   static myNewStaticMethod(){}
}

I just wonder if there is some easy way to add new methods to existing class and adding theirs declarations in Typescript (since for now I'm using both namespace and interface to handle it):

declare module './oldClass' {
  namespace oldClass { // allows adding static methods
    function myNewStaticMethod(value: string): string;
  }
  interface oldClass { // allows adding methods to prototype
    myNewMethod(value: string): string;
  }
}
wegry commented 3 years ago

I ran into something similar with dayjs plugins.

import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc' // dependent on utc plugin
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)

// Maybe there is a more ergonomic way to do this?
const withTz = (dayjs.extend(timezone) as unknown) as dayjs.Dayjs & {
  tz: ((tz: string) => typeof dayjs) & { guess: () => string }
}

const offset = dayjs().utcOffset()
console.log(withTz.tz.guess(), offset)

For libraries that do namespace(?) extension like jQuery used to, it's pretty painful setting up typings. You either get all the plugin types, or none of them.

https://stackoverflow.com/a/23225358/1924257 points out the need for a new class to extend an existing and third party interface.

RyanCavanaugh commented 2 months ago

The answer here is "declaration merging"