【问题标题】:Typescript interface generics extendsTypescript 接口泛型扩展
【发布时间】:2018-04-09 02:17:49
【问题描述】:

给定如下界面:

interface IMyInterface<T extends { [key: string]: number }> {
    target: T;
    to:  T;
}

我想把它改成类似的东西

interface IMyInterface<T extends { [key: string]: number }> {
    target: P extends T;
    to:  T;
}

其中 target 可以是任何对象,只要它包含 T 中定义的属性。

示例:

let obj = <IMyInterface>{
    target: { name:"John", age: 100 },
    to: { age: 200 }
}

打字稿编译器应该在以下情况下报错:

let obj = <IMyInterface>{
    target: { name:"John", y: 1980 },
    to: { x: 20 }
}

“目标”中缺少“x”属性的地方

let obj = <IMyInterface>{
    target: { name:"John", x: "20" },
    to: { x: 30 }
}

类型错误的地方(数字与字符串)

即我想要对目标进行类型检查,以便它包含“T”泛型中定义的所有属性。

我想要这个作为一个对象。

创建一个类似的方法

methodName<T extends { [key: string]: number }>(target: T, to: T){

}
// and calling it
methodName({ name:"John", x: "20" },{x:30});

即为“target”和“to”使用单独的参数可以工作,Typescript 中的类型检查是完美的,但我需要将“target”和“to”包含在一个对象中,这样我就可以使用如下方法:

methodName<IMyInterface>(object: IMyInterface){

并这样称呼它:

methodName({ target: { name:"John", age:20 }, to: { age: 30}});

感谢您的帮助

【问题讨论】:

    标签: typescript generics interface


    【解决方案1】:

    先说几句:首先,无论何时编写IMyInterface,都需要指定类型参数。类似的东西

    let obj = <IMyInterface> { // error
        target: { name:"John", age: 100 },
        to: { age: 200 }
    }
    

    总是会出错,因为您遗漏了类型参数。这将起作用:

    let obj = <IMyInterface<{ age: number }>> {
        target: { name:"John", age: 100 },
        to: { age: 200 }
    }
    

    或者,更习惯于 TypeScript,您注释变量的类型而不是断言值的类型:

    let obj: IMyInterface<{ age: number }> = {
        target: { name:"John", age: 100 },
        to: { age: 200 }
    }
    

    如果您对IMyInterface 的定义指定了default type parameter,则可以省略类型参数,但这不会按照您想要的方式工作,因为默认值始终相同,并且您想要更改的内容取决于to 属性的类型。


    我的下一个问题是:您是否希望 P 仅用于检查您是否正确创建了 IMyInterface&lt;T&gt;?所以一旦你创建了一个有效的IMyInterface&lt;T&gt;,你就可以在使用它时立即忘记P?它看起来像它。在这种情况下,我要做的是将P 排除在IMyInterface&lt;T&gt; 的定义之外,而是使用辅助方法。您注意到一个方法可以帮助您键入检查,但您想要一个对象,对吧?好吧,辅助方法可以返回一个有效的IMyInterface&lt;T&gt;

    namespace HelperFunctionSolution {
    
      // original definition
      interface IMyInterface<T extends { [key: string]: number }> {
        target: T;
        to: T;
      }
    
      // helper method
      function makeMyInterface<T extends { [key: string]: number }, P extends T>(
        target: P, 
        to: T
      ): IMyInterface<T> {
        return { target, to };
      }
    

    现在你可以使用你的辅助方法了:

      const goodObj = makeMyInterface({ name: "John", age: 100 }, { age: 200 }) // okay
    

    如果您检查goodObj,您会发现它是IMyInterface&lt;{age: number}&gt;。编译器会抱怨你想要的地方:

      const badObj0 = makeMyInterface({}, { x: 30 }) // x is missing
      const badObj1 = makeMyInterface({ name: "John", y: 1980 }, { x: 30 }) 
        // complains about extra property but real problem is x
    
      const badObj2 = makeMyInterface({ x: "20" }, { x: 30 }) // complains about string 
      const badObj3 = makeMyInterface({ name: "John", x: "20" }, { x: 30 }) 
        // complains about extra property but real problem is x
    

    现在你可以制作你想要的方法了:

      class Hmm {
        methodName<T extends { [key: string]: number }>(target: T, to: T) { /*impl*/ }
    

    等等,这是你的定义,但我认为你希望它采用这样的对象:

        realMethodName<T extends { [key: string]: number }>(
          myInterface: IMyInterface<T>
        ) { /*impl*/ }
      }
    

    并使用它,确保使用辅助函数

      const hmm = new Hmm();
      hmm.realMethodName(makeMyInterface({ name: "John", age: 20 }, { age: 30 })); // okay
    
    }
    

    您不能在其中使用对象字面量并保证类型检查,因为只有辅助函数关心P。所以这是一种方法,但您需要使用辅助函数。


    另一种可能性是您在IMyInterface 定义中随身携带P 类型。这可能不是您想要的,但它具有理想的效果,您可以在 methodName 方法中使用字符串文字并且不需要辅助函数:

    namespace TwoTypeParameterSolution {
    
      interface IMyInterface<T extends { [key: string]: number }, P extends T> {
        target: P;
        to: T;
      }
    
      class Hmm {
        realMethodName<T extends { [key: string]: number }, P extends T>(
          myInterface: IMyInterface<T,P>
        ) { /*impl*/ }
      }
      const hmm = new Hmm();
      hmm.realMethodName({target: { name: "John", age: 100 }, to: { age: 200 }}) // okay
      hmm.realMethodName({target: {}, to: { x: 30 }}) // x is missing
      hmm.realMethodName({target: { name: "John", y: 1980 }, to: { x: 30 }}) 
        // complains about extra property but real problem is x
    
      hmm.realMethodName({target: { x: "20" }, to: { x: 30 }}) // complains about string 
      hmm.realMethodName({target: { name: "John", x: "20" }, to: { x: 30 }}) 
        // complains about extra property but real problem is x
    
      hmm.realMethodName({ target: { name: "John", age: 20 }, to: { age: 30 } }); // okay
    }
    

    任何一种方式都应该适合你。希望有帮助;祝你好运!

    【讨论】:

    • 谢谢。这很棒。我需要这个的一个具体示例是例如为目标的某些属性设置动画。动画({目标:{名称:“矩形”,x:100,y:100,宽度:100,高度:100},到:{x:200,y:200}});我希望进行类型检查以确保您仅对存在于目标对象上的数字属性进行动画处理。
    猜你喜欢
    • 2015-04-01
    • 2019-04-06
    • 2021-02-14
    • 2021-02-06
    • 2021-08-07
    • 2020-09-12
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    相关资源
    最近更新 更多