Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/400.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何假定两个联合类型相同_Javascript_Typescript - Fatal编程技术网

Javascript 如何假定两个联合类型相同

Javascript 如何假定两个联合类型相同,javascript,typescript,Javascript,Typescript,我在做一个函数,它有两个和参数相同的并集类型。 在switch语句中,如何将它们假定为相同的类型 我正试着用这个Typescript@3.5.1 interface Square{ 种类:'方形' 尺码:号码 } 界面矩形{ 种类:'矩形' 宽度:数字 身高:多少 } 类型形状=方形|矩形 功能区(s:形状,ss:形状){ 如果(s.kind!==ss.kind)返回//检查它们的种类是否相同 开关(s.kind){ “方形”案例: 返回s.size*s.size+ss.size*ss.size

我在做一个函数,它有两个和参数相同的并集类型。 在switch语句中,如何将它们假定为相同的类型

我正试着用这个Typescript@3.5.1

interface Square{
种类:'方形'
尺码:号码
}
界面矩形{
种类:'矩形'
宽度:数字
身高:多少
}
类型形状=方形|矩形
功能区(s:形状,ss:形状){
如果(s.kind!==ss.kind)返回//检查它们的种类是否相同
开关(s.kind){
“方形”案例:
返回s.size*s.size+ss.size*ss.size//错误
案例“矩形”:
返回s.height*s.width+ss.height*ss.width//错误
}
}
此语句会产生如下错误:

类型“Shape”上不存在属性“size”

类型“矩形”上不存在属性“大小”。ts(2339)

但我预计不会发生错误,因为
s.kind

并且
ss.kind
已经被选中。

您得到了错误,因为
s
ss
在整个过程中都只是一个
形状。编译器知道两者都有一个名为“kind”的值,但仍然不知道它们的实际类型。
正方形
可能有“大小”,但
形状
没有,编译器知道它

创建一个需要知道
形状的详细信息的函数,首先会破坏使用
接口的目的。您可以更干净地实现您想要的目标,如下所示:

interface Square {
  area(): number
  size: number
}

interface Rectangle {
  area(): number
  width: number
  height: number
}

type Shape = Square | Rectangle

function areas(s: Shape, ss: Shape) {
   return s.area() + ss.area()
}
但是,如果您真的想这样做,您可以在访问每个对象的属性之前显式地将其转换为所需的类型

interface Square {
  size: number
}

interface Rectangle {
  width: number
  height: number
}

type Shape = Square | Rectangle

function areas(s: Shape, ss: Shape) {
    if (typeof s != typeof ss) {
        return
    }
    switch (typeof s) {
        case 'Square': {
            s = s as Square; ss = ss as Square
            return s.size * s.size + ss.size * ss.size
        }
        case 'Rectangle': {
            s = s as Rectangle; ss = ss as Rectangle
            return s.width * ss.height + s.width * ss.height
        }
    }
}
请注意,第二个示例实际上不起作用(即使您显式地将某个对象声明为联合类型之一),尽管编译了,因为
typeof
将返回
“object”
,但它演示了如何告诉编译器使用哪种类型(使用)

您可以尝试使用
instanceof
实现它:

if (s instanceof Square && ss instanceof Square) {
    s = s as Square; ss = ss as Square
    return s.size * s.size + ss.size * ss.size
}
// similar code for Rectangle etc
但是,Typescript不允许您在运行时检查对象是否实现了接口,因此您可以重新使用自定义类型保护:

interface Square {
    kind: string
    sameShape(obj: Shape): boolean 
    area(): number
    size: number
} 

class SquareImpl implements Square {
    kind: string = "square"
    size: number = -1
    area() { return this.size * this.size }
    sameShape(obj: Shape): obj is Square {
        return obj.kind == "square"
    }
    constructor(size: number) { this.size = size }
}
// similar for Rectangle

...
let r : Rectangle = new RectangleImpl(1, 2)
let s : Square = new SquareImpl(3)
let ss : Square = new SquareImpl(2)
if (s.sameShape(ss)) {
    console.log('s + ss: '+ s.area() + ss.area())
}
if (s.sameShape(r)) {
    console.log('s + r: '+ s.area() + r.area())
}

让编译器相信联合类型的两个变量是不容易的,在一般情况下甚至是不可能的。编译器几乎总是认为这两个值是独立的,除非您单独测试它们。意思:这里唯一的解决方案是这样的:您只需要做足够的工作来说服自己类型是相同的,并且您必须通过以下方式告诉编译器不要担心:

或者,您将不得不做比您认为必要的更多的工作,以便编译器确信唯一的可能性是您期望的。这意味着你会做你觉得多余的警卫。我认为在这种情况下,我会将您的代码重构为这样,这只会增加一个检查:

function areas2(s: Shape, ss: Shape) {
  if (s.kind === "square" && ss.kind === "square") {
    return s.size * s.size + ss.size * ss.size;
  }
  if (s.kind === "rectangle" && ss.kind === "rectangle") {
    return (
      s.height * s.width + ss.height * ss.width
    );
  }
  return;
}
好的,希望能有帮助。祝你好运


因此我看到了一些改进代码的方法

在我看来,只有当这两种类型是完全不同的类型时才应该使用。例如,CSS允许某些属性的值为字符串或数字。那么,您如何向函数的消费者传达您只希望他们通过这两个函数中的一个?对于联合类型,这是一个很好的例子:

var element: HtmlElement;Z
function bad(width: any) {
  element.style.width = width;
}
// no typescript error about the wrong type being passed in
bad(new Date());  

type widthType = string | number | null;
function good(width: widthType) {
  element.style.width = widthType
}
//  typescript error about the wrong type being passed in
good(new Date());  

虽然许多人决定使用
种类
属性,但我尽量避免使用它,因为它是一种。如果两种类型是兼容的,必须有人知道你的广场的相互作用,以建立自己的广场(yikes)。通过转移到抽象类,您可以从技术上避免这种情况:

abstract class Square {
  static kind = 'square'
}
但是,您可以只使用
instanceOf
,因此没有真正的意义

然而,在面向对象编程中,我们需要了解继承(is-a)和组合(has-a)。既然矩形和正方形都是形状,那么我们应该将对象建模为:

interface Shape { }
interface Rectangle : Shape { }
interface Square : Shape { }
现在我们有了一个很好的模型起点,我们需要看看这个方法。什么是区域?一个区域。因此,我们现在应该修改继承链/树/任何需要此功能的内容:

interface Shape { 
  areas(shape: Shape): number;
}
interface Rectangle : Shape { }
interface Square : Shape { }
我们在形状级别使用该方法,因为所有形状(假设为2D或更大)都有一个面积(0仍然是一个大小)

回顾这一点很容易,为什么形状要做这个计算,我只是建议许多框架(如果不是大多数OOP框架的话)做这个精确的事情。通过比较.Net中的两个对象时,应始终测试其类型是否相同。但请注意,该方法位于对象的根,而不是断开连接/全局方法

因此,这可能是这种改进的一个好结果:

interface Shape { 
  // null would indicate we can't join the two
  // I prefer null to indicate a known invalid value
  // and only use undefined to indicate an unknown (always invalid) value
  areas(shape: Shape): number | null;  
}
interface Rectangle : Shape { }
interface Square : Shape { }

class MyRectangle : Rectangle  {
  width: number;
  height: number;
  area(shape: Shape){
    if (!(shape instanceOf Rectangle)) {
      return null;
    }
    return this.height * this.width + shape.height * shape.width;
  }
}

class MySquare : Square {
  size: number;
  area(shape: Shape){
    if (!(shape instanceOf Square)) {
      return null;
    }
    return this.size * this.size + shape.size * shape.size;
  }
}

// Example call:

const mySquare = new MySquare();
const mySquare2 = new MySquare();
const areas = mySquare2.area(mySquare);  // fully type checked.
如果接口是一个单独的库,那么前面的示例是很好的,因为有些人可能希望以不同的方式表达这些值。如果不是这样,并且应该只有一种正方形和一种矩形,那么接口不是最佳选择,我建议使用类。因为在前面的示例中实现Circle将非常困难(接口和类都需要更改)。类的用法如下所示:

abstract class Shape {
  area(shape: Shape);
}
class Rectangle : Shape {
  width: number;
  height: number;
  area(shape: Shape){
    if (!(shape instanceOf Rectangle)) {
      return null;
    }
    return this.height * this.width + shape.height * shape.width;
  }
}
class Square: Shape {
  size: number;
  area(shape: Shape){
    if (!(shape instanceOf Square)) {
      return null;
    }
    return this.size * this.size + shape.size * shape.size;
  }
}
现在,实现循环变得微不足道

class Circle: Shape {
  radius: number;
  area(shape: Shape){
    if (!(shape instanceOf Circle)) {
      return null;
    }
    return this.size * this.size + shape.size * shape.size;
  }
}

根据需要更换您的if状态 如果(s.kind!==ss.kind)


希望它对您有用

在这个特定问题中使用它有好处吗?@ErikPhilips
instanceof
可以说是一个更好的选择,但这仍然不是最好的方法-只是尝试复制OP的代码,同时仍然显示不必使用属性来保存类型名等等,为什么
s.constructor.name
(它可能只是
“Object”
,因为没有任何东西表明存在
类正方形
类矩形
)而不是
s.kind
?这是一个近乎经典的例子;编译器当然明白,
abstract class Shape {
  area(shape: Shape);
}
class Rectangle : Shape {
  width: number;
  height: number;
  area(shape: Shape){
    if (!(shape instanceOf Rectangle)) {
      return null;
    }
    return this.height * this.width + shape.height * shape.width;
  }
}
class Square: Shape {
  size: number;
  area(shape: Shape){
    if (!(shape instanceOf Square)) {
      return null;
    }
    return this.size * this.size + shape.size * shape.size;
  }
}
class Circle: Shape {
  radius: number;
  area(shape: Shape){
    if (!(shape instanceOf Circle)) {
      return null;
    }
    return this.size * this.size + shape.size * shape.size;
  }
}