镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Suggestion: optional interface #371

Description

I think this is useful syntax :)

sample.

interface IFoo {
  foo: string; // not optional
}

class Bar implements IFoo? {
  // Bar does not have a foo property.
}

new Bar().foo;

How useful.

class Foo implements Lib.IListener? {
  constructor() { Lib.injectInto(this); }
}

module Lib {
  export interface IListener {
    on(data: any): void;
  }

  export function injectInto(target: any) {
    target.on = () => { /* do something... */ }; 
  }

  export function listen(receiver: IListener) {
    receiver.on("foobar");
  }
}

var obj = new Foo();
Lib.listen(obj);

Lib.listen({
  on: ()=> {} // on method required
});

In real world.

case 1.
https://github.com/borisyankov/DefinitelyTyped/blob/705102df2b652bdbadad23561675116a26e48760/space-pen/space-pen.d.ts#L52

case 2.
// Emissary#emitter
https://github.com/atom/emissary#emitter
// .d.ts
https://github.com/borisyankov/DefinitelyTyped/blob/b4aba562ef4192bc3f2e770ab2018f89e047836b/emissary/emissary.d.ts#L13
// usage in TypeScript (dirty hack!)
https://github.com/vvakame/language-review/blob/d23f8797bbb6e99d636399a14c7035c4809ab365/lib/util/emissary-helper.ts#L11

Activity

  1. basarat commented on Aug 6, 2014

    @basarat
    Contributor

    This is an alternative syntax suggestion for #340

    But still 👍

  2. basarat commented on Aug 6, 2014

    @basarat
    Contributor

    Admittedly this way we do not break existing code.

  3. basarat commented on Aug 6, 2014

    @basarat
    Contributor

    Another use case is making mixins easier

  4. RyanCavanaugh commented on Aug 6, 2014

    @RyanCavanaugh
    Member

    Major question I have here is that an important use case for mix-ins is that you would be able to add one of these optional interfaces after the fact, which doesn't work well with having to declare this on the original class declaration. A solution for this JS pattern should be able to handle both cases.

  5. vvakame commented on Aug 7, 2014

    @vvakame
    ContributorAuthor

    Sorry English is not my first language.
    Do you say How to mix-in interface to already defined class??

    I think we should expand TypeReference grammer.
    TypeName [no LineTerminator here] TypeArgumentsopt to TypeName [no LineTerminator here] TypeArgumentsopt ImplementsClauseopt.

    use case.

    module Lib {
      export interface IListener {
        on(data: any): void;
      }
    
      export function injectInto<T>(target: T): T implements IListener {
        var modified = <T implements IListener?> target;
        modified.on = () => { /* do something... */ }; 
        return modified; // IListener? has compatiblity to IListener. I think this is programmer's duty.
      }
    
      export function listen(receiver: IListener) {
        receiver.on("foobar");
      }
    }
    
    var receiver = Lib.injectInto({ name: "vvakame" });
    // typeof receiver === ({name: string;} implements IListener)
    // I want to get expanded object type literal like {name: string; on(data: any): void;} on error message.
    receiver.on("foobar");
    receiver.name;
    Lib.listen(receiver);
    
  6. s-panferov commented on Sep 15, 2014

    @s-panferov

    +1 to something that can fix the current TypeScript mixins issues. Things like ReactJS using mixins widely and now it is very hard to make TypeScript compiler check all this things property without some effort.

    For example, there are more than 400 components in my project and it will be a little bit crazy to repeat React's method defs inside every component. On the other hand i want to type-check my code. So i find out to define classes like this:

    class LinkComponent extends ReactComponentBase<LinkComponentOptions,{}> implements React.ReactComponentSpec<LinkComponentOptions,{}> {
       // extends Component<LinkComponentOptions,{}> for instance methods like setState
       // implements React.ReactComponentSpec<LinkComponentOptions,{}> for type check livetime methods
    }
    
     // this is an `empty` class to satisfy the compiler
     export class ReactComponentBase<P, S> implements React.ReactComponent<P, S> {
        state: S;
        props: P;
        refs: { [ref: string]: React.ReactComponent<any, any>; };
        getDOMNode: () => Element;
        setProps: (nextProps: P, callback?: () => void) => void;
        replaceProps: (nextProps: P, callback?: () => void) => void;
        transferPropsTo: <C extends React.ReactComponent<P, any>>(target: C) => C;
        setState: (nextState: S, callback?: () => void) => void;
        replaceState: (nextState: S, callback?: () => void) => void;
        forceUpdate: (callback?: () => void) => void;
      }

    This leads to emty class ReactComponentBase that includes to every component (in compiled js code) and looks very ugly. And for my project's react mixins i need to create additional (empty) classes that extends ReactComponentBase and provide correct mixin's methods.

    export class MixinsComponent<P, S> extends ReactComponentBase<P, S> {
        // example of mixin method
        onValue: (bus: Bacon.Bus<any>, foo: (val: any) => void) => void;
    }

    I think that implementation of normal mixins and unit types will make TypeScript much more friendly to such kind of libs.

  7. sedwards2009 commented on Sep 15, 2014

    @sedwards2009

    Dojo is another example of a big library which could benefit greatly from TypeScript but also makes extensive use of mixins. It is not uncommon for classes in an application to require the use of 3 extra mixins. Using Dojo with TypeScript isn't really practical at the moment.

  8. nycdotnet commented on Sep 24, 2014

    @nycdotnet

    Just chiming in that this would be really useful. I had requested something like this on CodePlex last September.

    https://typescript.codeplex.com/workitem/1631

  9. duanyao commented on Jun 5, 2015

    @duanyao

    I love this suggestion because it is also useful for creating UniversalElement (#3304).

  10. benliddicott commented on Jul 15, 2015

    @benliddicott

    Is this need met by Intersection Types #3622 (committed)? See also Feature: type alternatives (to facilitate traits/mixins/type decorators) #727, aiming at the same target.

    Syntax: A & B is a type which implements both A and B. (In other words roughly equivalent to interface A_n_B implements A, B{}) It can be used wherever union types can be used.

    Synopsis:

    interface Emitter{ 
      on(event, fn);
      once(event, fn);
      off(event, fn);
      emit(event);
    }
    
    function MakeEmitter<T>(o T: ( T & Emitter) {
       require('my-emitter-library').mixin(o);
       return <T & Emitter>o;
    }
    

    Or you could provide a .d.ts which typed the function mixin as returning the appropriate intersection type.

  11. unional commented on Jun 27, 2016

    @unional
    Contributor

    How about this use case?
    I can't find a way to do this:

    interface Foo {
      foo: string;
    }
    
    interface Boo extends Foo? {
      boo: string;
    }
    
    let x: Boo = getBoo();
    if (x.foo) {
      let y: Foo = x;
      ...
    }

    Essence: I want to put make foo and optional property in Boo, but still able to type y properly.

    Here is an example that show current error when this is missing:

    interface Config<S> {
      initialize<S>(...);
      reduce<S>(state: S, action: any): S;
      ...
    }
    
    class Foo<S> {
      configs: Config<S>[] = [];
      reducers: Config<S>[] = []; // <-- Could have been Reducer<S>[];
      addConfig(config: Config<S>) {
        this.configs.push(config);
        if (config.reduce) {
          this.reducers.push(config);
        }
      }
    
      do(state: S, action: any) {
        return this.reducers.reduce((s, reducer) => {
          // Object is possibly 'undefined' (with `strictNullCheck`)
          return reducer.reduce(s, action);
        }, state);
      }
    }

    Currently a workaround is to duplicate the signature of reduce() in interface Reducer<S> { } and make it not optional.

  12. guillaume86 commented on Oct 21, 2016

    @guillaume86

    It's also useful for options parameters or stricts merge typechecks.

    A very common pattern is to define default options object with values for all required properties
    and when the user provide his options, do something like Object.assign({}, defaults, userOpts).

    It would be nice to internally use IOptions with some required properties, and expose the API to the user with IOptions? (all properties optional).

    This would also be useful in redux when Object.assign is used as an immutable merge.

  13. aluanhaddad commented on Oct 24, 2016

    @aluanhaddad
    Contributor

    This kind of type operator would be extremely useful in many cases as discussed above. One that occurs to me and I don't think it's been mentioned, is simply sending and receiving isomorphic data from a JSON service.

    Generally when you do this you want anothing interface with optional properties to compose requests and the exact same interface except with required properties to type responses.

    function getUser(id: number): Promise<User>;
    
    function updateUser(user: User?): Promise<void>;

    It would be especially powerful when mixed with subset types and intersection types.

    I don't think it should be limited to interfaces I think it should apply to type aliases.

  14. RyanCavanaugh commented on Jul 21, 2020

    @RyanCavanaugh
    Member

    This is now

    class Bar {
      // Bar does not have a foo property.
    }
    interface Bar extends Partial<IFoo> { }
    
    new Bar().foo;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoThe issue still hasn't been fully clarifiedSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions