+
Skip to content

qiwi/mixin

@qiwi/mixin

RnD project to compare various mixin approaches in TypeScript.

Build Status Maintainability Test Coverage npm (tag)

Usage

import {applyMixins} from '@qiwi/mixin'

interface IA {
  a: () => string
}
interface IB {
  b: () => string
}
class A implements IA {
  a() { return 'a' }
}
const b: IB = {
  b() { return 'b' }
}

const c = applyMixins({}, A, b)
c.a() // 'a'
c.b() // 'b'

const D = applyMixins(A, b)
const d = new D()

d.a() // 'a'
d.b() // 'b'

Details are illustrated by the advanced usage examples.

The library exposes itself as cjs, esm, umd and ts sources. Follow packages.json test:it:* scripts and integration tests examples if you're having troubles with loading.

Questions

  1. Definition.

    A mixin is a special kind of multiple inheritance.

  2. Is it possible to mix classes with automated type inference?

    There're several solutions:

    • A subclass factory
    • Proto merge + constructor invocation + type cast workarounds
  3. How to combine OOP and functional mixins?

    Apply different merge strategies for each target type and rest args converters

  4. How to check if composition has a given mixin or not?

    Ref Cache / WeakMap

  5. What's about mixin factories?

    It's called applyMixins

Definition

A mixin is a special kind of multiple inheritance. It's a form of object composition, where component features get mixed into a composite object so that properties of each mixin become properties of the composite object.

In OOP, a mixin is a class that contains methods for use by other classes, and can also be viewed as an interface with implemented methods.

Functional mixins are composable factories which connect together in a pipeline; each function adding some properties or behaviors.

Perhaps these are not perfect definitions, but we'll rely on them.

How to and what to mix

  1. Subclass factory

    type Constructor<T = {}> = new (...args: any[]) => T
    
    function MixFoo<TBase extends Constructor>(Base: TBase) {
      return class extends Base {
        foo() { return 'bar' }
      }
    }
  2. Prototype injection

    class Derived {}
    class Mixed {
      foo() { return 'bar' }
    }
    
    Object.getOwnPropertyNames(Mixed.prototype).forEach(name => {
        Object.defineProperty(Derived.prototype, name, Object.getOwnPropertyDescriptor(Mixed.prototype, name));
    })
  3. Object assignment

    const foo = {foo: 'foo'}
    const fooMixin = (target) => Object.assign(target, foo)
    const bar = fooMixin({bar: 'bar'})
  4. Proxy wrapping

    const mixAsProxy = <P extends IAnyMap, M extends IAnyMap>(target: P, mixin: M): P & M => new Proxy(target, {
      get: (obj, prop: string) => {
        return prop in mixin
          // @ts-ignore
          ? mixin[prop]
          // @ts-ignore
          : obj[prop]
      },
    }) as P & M
  5. Functional mixin piping

    const foo = <T>(t: T): T & {foo: string} => ({...t, foo: 'foo'})
    const bar = <T>(t: T): T & {bar: number} => ({...t, bar: 1})
    const foobar = pipe(foo, bar) // smth, that composes fn mixins like `(target) => bar(foo(target))`
    const target = {}
    
    const res = foobar(target)

Refs

About

RnD project to compare various mixin approaches in TypeScript

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors 5

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载