百科狗-知识改变命运!
--

混入类型(Mixins) - TypeScript 类的类型

是丫丫呀1年前 (2023-11-21)阅读数 20#技术干货
文章标签属性

混入类型(Mixins)

你可能在 Scala 等语言里对mixinstrait已经很熟悉了,这种模式在 JavaScript 中也是很流行的。

在 TypeScript 中,可以根据不同的功能定义多个可复用的类,然后组合(或者扩展)这些可复用的类,从而快速搭建起一个功能强大的类。这就是mixins模式。

组合(或者扩展)可复用类,常借助extendsimplementsextends只支持继承一个父类,implements可以连接多个可复用的类,并且使用原型链连接子类的方法和父类的方法。

Mixin 如何工作?

该模式依赖于使用具有类继承的泛型扩展基类。TypeScript 最好的 mixin 支持是通过类表达式模式实现的。

开始,我们需要一个基类,在基类的基础上应用 mixin:

class Sprite {
  name = "";
  x = 0;
  y = 0;
 
  constructor(name: string) {
    this.name = name;
  }
}

然后需要一个类型(type)和一个工厂函数(factory function),它返回一个扩展基类的类表达式

// To get started, we need a type which we'll use to extend
// other classes from. The main responsibility is to declare
// that the type being passed in is a class.
 
type Constructor = new (...args: any[]) => {};
 
// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:
 
function Scale(Base: TBase) {
  return class Scaling extends Base {
    // Mixins may not declare private/protected properties
    // however, you can use ES2020 private fields
    _scale = 1;
 
    setScale(scale: number) {
      this._scale = scale;
    }
 
    get scale(): number {
      return this._scale;
    }
  };
}

设置好这些之后,您可以创建一个类,该类表示应用 mixin 的基类:

// Compose a new class from the Sprite class, with the Mixin Scale applier:
const EightBitSprite = Scale(Sprite);
 
const flappySprite = new EightBitSprite("Bird");
flappySprite.setScale(0.8);
console.log(flappySprite.scale);


受约束的混入

在上面的表单中,mixin 没有类的基础知识,这会使您很难创建所需的设计。为了对此建模,我们修改原始构造函数类型以接受泛型参数。

// This was our previous constructor:
type Constructor = new (...args: any[]) => {};
// Now we use a generic version which can apply a constraint on
// the class which this mixin is applied to
type GConstructor = new (...args: any[]) => T;

这允许创建仅使用受约束基类的类:

type Positionable = GConstructor void }>;
type Spritable = GConstructor;
type Loggable = GConstructor void }>;

然后,您可以创建 mixin,只有当您有一个特定的基础来构建时,它才能工作:

function Jumpable(Base: TBase) {
  return class Jumpable extends Base {
    jump() {
      // This mixin will only work if it is passed a base
      // class which has setPos defined because of the
      // Positionable constraint.
      this.setPos(0, 20);
    }
  };
}


备选模式

本文档的早期版本推荐了一种编写 mixin 的方法,您可以分别创建运行时和类型层次结构,然后在最后合并它们:

// Each mixin is a traditional ES class
class Jumpable {
  jump() {}
}
 
class Duckable {
  duck() {}
}
 
// Including the base
class Sprite {
  x = 0;
  y = 0;
}
 
// Then you create an interface which merges
// the expected mixins with the same name as your base
interface Sprite extends Jumpable, Duckable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);
 
let player = new Sprite();
player.jump();
console.log(player.x, player.y);
 
// This can live anywhere in your codebase:
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}

混入类型(Mixins) - TypeScript 类的类型

此模式较少依赖编译器,更多依赖代码库,以确保运行时和类型系统正确保持同步。


约束条件

通过代码流分析,在 TypeScript 编译器内部本机支持 mixin 模式。在一些情况下,您可以触及本机支持的边缘。

装饰器和混入

您不能使用修饰符通过代码流分析提供 mixin:/p>

// A decorator function which replicates the mixin pattern:
const Pausable = (target: typeof Player) => {
  return class Pausable extends target {
    shouldFreeze = false;
  };
};
 
@Pausable
class Player {
  x = 0;
  y = 0;
}
 
// The Player class does not have the decorator's type merged:
const player = new Player();
player.shouldFreeze;
Property 'shouldFreeze' does not exist on type 'Player'.
 
// The runtime aspect could be manually replicated via
// type composition or interface merging.
type FreezablePlayer = Player & { shouldFreeze: boolean };
 
const playerTwo = (new Player() as unknown) as FreezablePlayer;
playerTwo.shouldFreeze;


静态属性混入

与其说是约束,不如说是抓住了。类表达式模式创建单例,因此不能在类型系统中映射它们以支持不同的变量类型。

您可以通过使用函数返回基于泛型的不同类来解决此问题:

function base() {
  class Base {
    static prop: T;
  }
  return Base;
}
 
function derived() {
  class Derived extends base() {
    static anotherProp: T;
  }
  return Derived;
}
 
class Spec extends derived() {}
 
Spec.prop; // string
Spec.anotherProp; // string


对象混入

使用 es6 的Object.assign合并多个对象。

interface Name {
    name: string
}
interface Age {
    age: number
}
interface Sex {
    sex: number
}
 
let people1: Name = { name: "张三" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }
 
const people = Object.assign(people1,people2,people3)
//people 会被推断成一个交差类型 Name & Age & sex;


类的混入

首先声明两个 mixins 类(严格模式要关闭不然编译不过)。使用implements,把类当成了接口。我们可以这么做来达到目的,为将要 mixin 进来的属性方法创建出占位属性。这告诉编译器这些成员在运行时是可用的。

class A {
    type: boolean = false;
    changeType() {
        this.type = !this.type
    }
}
 
 
class B {
    name: string = '张三';
    getName(): string {
        return this.name;
    }
}

class C implements A,B{
    type:boolean
    changeType:()=>void;
    name: string;
    getName:()=> string
}

最后,创建这个帮助函数,帮我们做混入操作。它会遍历 mixins 上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码。

Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名。

Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
    itemCls.forEach(item => {
        Object.getOwnPropertyNames(item.prototype).forEach(name => {
            curCls.prototype[name] = item.prototype[name]
        })
    })
}

let c = new C()

鹏仔微信 15129739599 鹏仔QQ344225443 鹏仔前端 pjxi.com 共享博客 sharedbk.com

免责声明:我们致力于保护作者版权,注重分享,当前被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理!邮箱:344225443@qq.com)

图片声明:本站部分配图来自网络。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

内容声明:本文中引用的各种信息及资料(包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主体(包括但不限于公司、媒体、协会等机构)的官方网站或公开发表的信息。部分内容参考包括:(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供参考使用,不准确地方联系删除处理!本站为非盈利性质站点,本着为中国教育事业出一份力,发布内容不收取任何费用也不接任何广告!)