分享一个能在属性检查器上选择字段类型的装饰器

假设我们有一个类,它有一个表示形状的字段,形状可以是圆形、矩形(或者更多其他的类型)。我们希望在属性检查器配置这个字段的时候,可以选择一种。比如选择圆,就展示它的半径;选择矩形就能配置它的宽和高。

我们可以怎么做呢?

class Shape {}
class Circle extends Shape { radius = 0.0; } // 圆形
class Rectangle extends Shape { width = 0.0; height = 0.0; } // 矩形

class Demo { shape: Shape = new Circle(); /* 表示形状的字段 */ }

一种方法是创建一个枚举字段表示形状类型,然后在这个类型字段的访问器里做一些事:

enum ShapeType { Circle, Rectangle }

class Demo {
  @property // 在属性检查器中显示这个字段
  get shapeType() { return this._shapeType; }

  set shapeType(value) {
    // 切换类型时,重新创建 `this.shape`
    if (this._shapeType !== value) {
      this._shapeType = value;
      if (value === ShapeType.Circle) {
        this.shape = new Circle();
      } else /* ... */ {
        /* ... */
      }
    }
  }

  private _shapeType = ShapeType.Circle;
}

这是目前解决此类问题广泛使用的方法。

这个方法有一个缺陷,就是它引入了一个额外的 this._shapeType 字段。我们可以改良一下这个实现,以免去 _shapeType 的存储——用 this.shape 来推导当前的形状类型:

class Demo {
  @property
  get shapeType() {
    // 根据 `this.shape` 推断 `ShapeType`
    if (this.shape.constructor === Circle) { return ShapeType.Circle; }
    else /* ... */ { /* ... */ }
  }

  set shapeType(value) {
    if (this.shapeType !== value) {
      if (value === ShapeType.Circle) {
        this.shape = new Circle();
      } else /* ... */ {
        /* ... */
      }
    }
  }
}

改良后的方法已经能解决我们的问题了。不过,当可选择的类型变多时,这里就要写很多的 if - else 分支,很麻烦。有鉴于此,我们可以将这个“生成类型”的功能做成一个装饰器,然后开箱即用。

我先给出使用方法和效果,然后在文章的最后给出实现代码。

使用方法

@property // 这个 @property 和平常的装饰器一样,而且不是必须;它控制 shape 本身的展示。
@polymorphism({ // 为当前装饰的字段生成一个类型字段。
    types: [ // 允许的类型项
        [Circle, '圆形'], // 圆形类以及它的展示名称
        [Rectangle, '矩形'], // 矩形类以及它的展示名称
    ]
})
public shape: Shape = new Circle();

效果:

(录屏的时候下拉框没录下来)

Demo

实现:

PolymorphismDemo.zip (1.2 KB)

7赞

开工大吉,马克一下!

开工大吉,马克两下!

有没有快捷的方式,可以添加按钮,现在用勾选框代替 :sweat_smile:
目前我们通过继承所有UI组件的方式,给未来修bug和扩展基础组件预留扩展空间,目前继承默认组件属性展示器里面会显示异常,并且不好给自己新增的属性定制属性检查器显示方式

用 3.3.2 的表示直接对 shape 赋值的方式更新不了inspector,只有对数组操作才能更新,新版用直接赋值兼容不了老版本