Coder Social home page Coder Social logo

michaelolof / typescript-mix Goto Github PK

View Code? Open in Web Editor NEW
87.0 4.0 8.0 888 KB

A tweaked implementation of TypeScript's default applyMixins(...) idea using ES7 decorators

Home Page: https://www.npmjs.com/package/typescript-mix

TypeScript 97.18% Shell 2.82%
typescript typescript-mixins traits-decorator mixins

typescript-mix's Introduction

TypeScript Mix

A tweaked implementation of TypeScript's default applyMixins(...) idea using ES7 decorators.

Breaking Changes from Version 3.0.0 upwards

  • New decorator @delegate introduced
  • Changes made in how multiple mixins implementing the same method are mixed

See Breaking Changes Explained

Dependencies

  • TypeScript
  • ES7 Decorators

Installation

npm install --save typescript-mix

Features

Goals

  • Ensure programming to an interface and not just only multiple implementations.

  • Create simple mixins that implement that interface

  • Provide an intuitive and readable way of using such mixins in a concrete class.

Why I wrote yet another Mixin Library.

The mixin pattern is somewhat a popular pattern amongst JavaScript/TypeScript devs as it gives the power of "mixin in" additional functionality to a class. The official way of using mixins as declared by Microsoft in TypeScript can be really verbose to downright unreadable.

How to use

The 'use' decorator

Program to an interface.

interface Buyer {
  price: number
  buy(): void
  negotiate(): void
}

Create a reusable implementation for that interface and that interface alone (Mixin)

const Buyer: Buyer = {
  price: undefined,
  buy() {
    console.log("buying items at #", this.price );
  },
  negotitate(price: number) {
    console.log("currently negotiating...");
    this.price = price;
  },
}

Define another mixin this time using a Class declaration.

class Transportable {
  distance:number;
  transport() {
    console.log(`moved ${this.distance}km.`);
  }
}

Define a concrete class that utilizes the defined mixins.

import use from "typescript-mix";

class Shopperholic {
  @use( Buyer, Transportable ) this
  
  price = 2000;
  distance = 140;
}

const shopper = new Shopperholic();
shopper.buy() // buying items at #2000
shopper.negotiate(500) // currently negotiating...
shopper.price // 500
shopper.transport() // moved 140km

What about intellisense support?

We trick typescript by using the inbuilt interface inheritance and declaration merging ability.

interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  @use( Buyer, Transportable ) this
  
  price = 2000;
  distance = 140;
}

The 'delegate' decorator

The delegate decorator is useful when we want specific functionality mixed into the client.

class OtherClass {
  simpleMethod() {
    console.log("This method has no dependencies");
  }
}

function workItOut() {
  console.log("I am working it out.")
}

class MyClass {
  @delegate( OtherClass.prototype.simpleMethod )
  simpleMethod:() => void

  @delegate( workItOut ) workItOut:() => void
}

const cls = new MyClass();
cls.simpleMethod() // This method has no dependencies
cls.workItOut() // I am working it out

Things to note about this library?

  • using the 'use' decorator mutates the class prototype. This doesn't depend on inheritance (But if you use mixins correctly, you should be fine)

  • mixins don't override already declared methods or fields in the concrete class using them.

  • Mixins take precedence over a super class. i.e. they would override any field or method from a super class with the same name.

  • instance variables/fields/properties can be declared or even initialized in your mixins. This is necessary if you're defining methods that depend on object or class properties but these properties won't be mixed-in to the base class so you have to redefine those properties in the base class using the mixin.

Advantages

  • The Library is non-obtrusive. Inheritance still works, (multiple inheritance still works ('Real Mixins Style')).

The delegate decorator

The addition of the delegate decorator now means module is imported as:

import { use, delegate } from "typescript-mix"

Multiple Mixins with the same method.

Consider the following piece of code. alt text

Cleint One uses two mixins that contain the same method mixIt(). How do we resolve this? Which method gets picked?. One advantage of extending interfaces as we've defined above is that we're essentially telling TypeScript to mix-in the two mixin interfaces into the ClientOne interface. So how does TypeScript resolve this?

alt text

Notice that TypeScript's intellisense calls MixinOne.mixIt() method. Therefore to be consistent with TypeScript and avoid confusion the '@use' decorator also implements MixinOne.mixIt() method.

typescript-mix's People

Contributors

michaelolof avatar nioperas06 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

typescript-mix's Issues

This expression is not callable.

ERROR in D:/git/aca-stages/src/components/InternshipsSchedule.vue(994,4):
994:4 This expression is not callable.
  Type 'typeof import("D:/git/aca-stages/node_modules/typescript-mix/dist/index")' has no call signatures.
    992 | })
    993 | export default class InternshipsSchedule extends Vue {
  > 994 |   @use( stepsMixin ) (this as any)
        |    ^
    995 | 
    996 |   dataLevelsIsLoading = false
    997 | 
Version: typescript 3.9.7
Time: 780ms

@use() decorator on class

I think it would kind off make more sense for the @use decorator to be applied on classes instead of a property 'this:any'.

So for example:

class Mixable {}

@use(Mixable)
class Mixed {}

interface Mixed extends Mixable {}

[ts] Member 'this' implicitly has an 'any' type

When I use code from the example it throws me an error on this statement: [ts] Member 'this' implicitly has an 'any' type

class Shopperholic {
    @use( Buyer, Transportable ) this
    
    price = 2000;
    distance = 140;
  }

I have made the "noImplicitAny": false in tsconfig and all is ok but wonder - is it a valid behaviour?

Suggestion - not a bug - Is it possible to add testing examples for this library?

Great work here, very simple intuitive API.

I read on your medium article that it would be easy to test mixins to by instantiating them.

Would you be able to push any examples tests inside an example folder in this repo, or would you be able to share a code-snippet as it would be very useful.

I hope to share your work with others and introduce them to this great concept.

Usage without decorators?

This looks really cool! Can I use this without decorators? I'm using sucrase for fast builds, but it doesn't support decorators (since it's not fully standardized).

booleans?

Any reason we can't mixin boolean properties? Thanks!

p.s. Great library - the best I've found for TypeScript that has Intellisense working nicely!

Mixins don't work with getters

I just started using this library today. I love it so far. Thanks for maintaining it.

From what I can tell, typescript getters don't work with mixins (at least in my specific case). Here is an example I am trying to get to work.

class EmployableMixin {
  config: any;
  get companyId(): string {
    return this.config.companyId; // TypeError: Cannot read property 'companyId' of undefined
  }
}

class Employee {
  @use(EmployableMixin)

  config: any;
  constructor(config) {
    this.config = config;
  }
}

const employee = new Employee({companyId: '12345'});
console.log(employee.companyId) // error

However, if I edit the mixin to use a regular function rather than a getter, the error goes away:

class EmployableMixin {
  config: any;
  getCompanyId(): string {
    return this.config.companyId; // no error
  }
}

class Employee {
  @use(EmployableMixin)

  config: any;
  constructor(config) {
    this.config = config;
  }
}

const employee = new Employee({companyId: '12345'});
console.log(employee.getCompanyId()) // 12345

I realize I'm probably doing something unconventional by trying to access the config object in the mixin class. But if it works with a regular function I feel like it should work with a getter as well. Do you know if this would be possible to fix? I'm happy to help out with a pull request. I just wanted to get your thoughts first.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.