typescript 如何定义带有'px'后缀的自定义类型

u59ebvdq  于 2022-11-30  发布在  TypeScript
关注(0)|答案(4)|浏览(226)

我知道如何定义一个Position类型的类:

class Position {
  x: number = 0;
  y: number = 0;
}

但现在我需要把xy的值都取一个以px为后缀的整数,如:

const position = {
  x: '1px',
  y: '2px'
}

如何在TypeScript中定义这样的类型?

bzzcjhmw

bzzcjhmw1#

你不能。这将是一个dependent type,它不仅没有出现在Typescript中,而且你必须远远超出普通语言的范围才能找到。最有名的具有依赖类型的语言可能是Idris
就实际解决问题而言,最好的解决方案可能是这样的:

const VALID_XY_VAL = /^(-?\d+)px$/;

class Point {
  _x: number = 0;
  _y: number = 0;

  constructor(x: string, y: string) {
    // NOTE: not _x and _y, we want to invoke the
    // setters here
    this.x = x;
    this.y = y;
  }

  get x () {
    return `${this._x}px`;
  }

  get y () {
    return `${this._y}px`;
  }

  set x(xVal: string) {
    const num = xVal.match(VALID_XY_VAL);
    if (num === null) throw new Error('Invalid value');
    this._x = Number(num[1]);
  }

  set y(yVal: string) {
    const num = yVal.match(VALID_XY_VAL);
    if (num === null) throw new Error('Invalid value');
    this._y = Number(num[1]);
  }
}

const p = new Point('1px', '2px'); // Point<1, 2>
p.x; // '1px'
p.y; // '2px'
p.x = '5px'; // a-ok
p.y = '5ac3px'; // KABOOM! Error.

这会将像素值储存为内部(且类型安全)的数字。如果您尝试设定无效的值,它会在执行阶段掷回。
typescript Playground
Regex101

编辑

Altocumulus在注解中指出,正则表达式应该是Point类的一个只读静态成员。每种方法都有优缺点:静态成员遵循最小特权原则,但会妨碍重用(例如,对于同一个文件中具有像素值的另一个类或函数)。我认为一个声明的常量模块级不可变(以任何方式)值就足够了,但YMMV。

class Point {
  private static readonly VALID_VALUE: RegExp = /.../
pgccezyw

pgccezyw2#

你可以尝试使用这种方法:

type HeightUnit = '%' | 'px' | 'em' | 'vh';
type HeightProp = `${number}${Unit}`

结果:

myHeight: HeightProp;
myHeight = '10px'  --- ok
myHeight = '10'  --- compilation error
mrzz3bfm

mrzz3bfm3#

编辑,推理答案

你们中的一些人可能认为这不是给定问题的答案。但事实是,作为开发人员,我们有责任对我们的数据结构进行建模。提问者在这里提出了一个非常具体的问题,即如何在typescript中对"{number}px"类型进行建模。答案是一个--这是不可能的。但问题之所以是这样,是因为提问者认为这种类型是他的问题的解决方案。什么是误导。真正的问题是如何在类型系统中用两个数值x,y和单位表示二维点。这可以通过简单的[number, number, 'px']来实现,其中我们需要两个数字,单位是静态的“px”。这种结构是灵活的和类型安全的。使用一些正则表达式,具有引发异常的setter和getter的类不需要编译步骤,但需要复杂的运行时验证。

原始答案:

为什么你需要一个类来表示一个对。用类来表示这样简单的结构就像用锤子打死一只苍蝇。你真正需要的是简单的对+单位表示,它可以表示为元组[a, b, unit]或记录{a:a, b:b, unit: unit}。正如其他人也指出的,没有可能定义带有'px'后缀的数字的类型。但是你可以用很多方法来模拟。
1.命题一-使用附加单位信息建模

// modeling as 3-nd tuple
type Point = [number, number, string]
const point = [1,2,'px']

// modeling as key-value map 
type Point = {x: number, y: number, unit: string}
const point = {x:1,y:2,unit:'px'}

2.用严格单元类型建模。如果我们确定会有像'px'这样的严格单元,它可以在类型中定义。为了不重复我自己,我将只在键-值Map类型中显示示例。

type Point = {x: number, y: number, unit: 'px'} // unit property can be only px
const point = {x:1,y:2,unit:'px'}

我们也可以创建点构造函数,以避免手动放置px:

const createPoint = (x: number, y: number):Point => ({x,y,unit:'px'});
const point = createPoint(1,2) // {x:1,y:2,unit:'px'}

3.建模为字符串对,但使用构造函数我们可以将类型保留为字符串对,但通过数字构造函数生成

type Point = {x: string, y: string}
const createPoint = (x: number, y: number):Point => ({x: x + 'px', y: y + 'px'});
const point = createPoint(1,2) // {x:'1px',y:'2px'}

4.使用特殊get函数作为对象建模

type Point = {values: () => {x: number, y: number}, pixels: () => {x: string, y: string}}
// below is using closure and stored in it arguments of createPoint function
const createPoint = (x:number, y:number): Point => ({
  values: () => ({x, y}),
  pixels: () => ({x: x + 'px', y: y + 'px'})
})
const point = createPoint(1,2);
point.values() // {x: 1, y: 2}
point.pixels() // {x: '1px', y: '2px'}
uujelgoq

uujelgoq4#

您可以像以前那样定义具有数值x和y属性的类型,也可以定义允许数字和字符串的其他类型。

class Position {
  x: number | string = 0;
  y: number | string = 0;
}

相关问题