• TypeScript


    什么是泛型

    官方是这样介绍的:

    软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
    在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

    从上面的信息概括为泛型是支持多种类型的变量,根据用户需求灵活的变动,达到复用的效果。
    在实际开发中,函数是同样的逻辑,只是因为类型的不同,可能要再写一个函数,这样的问题很糟糕。正好泛型就可以用来解决这种问题。

    举例

    现在有一个处理数组的函数,这里只是简单的返回,当做进行处理

    function formatArr(arr:Array<string>):Array<string>{
      return arr.map(item=>item)
    }
    

    如果这时候有其他类型的数组需要同样的操作,而 formatArr 只接受字符串数组,我们有什么方法来解决呢?

    第一种:any

    函数接受一个任意类型的数组,这确实能够解决问题,但是也失去了类型检查的意义

    function formatArr(arr:Array<any>):Array<any>{
      return arr.map(item=>item)
    }
    

    第二种:重新定义函数

    再写一个同样逻辑的函数,这看起来就不妥。如果还要接受更多的类型,而函数内部的逻辑复杂,这样重复定义多个同样逻辑的函数,会显得代码的冗余

    function formatArr2(arr:Array<number>):Array<number>{
      return arr.map(item=>item)
    }
    

    正解:泛型

    泛型函数的类型与非泛型函数的类型没什么不同,只是声明一个类型参数在最前面。因为类型参数相当于变量,我们不必在函数定义时就定义类型,而是执行时由使用者规定类型。

    function formatArr<T>(arr:Array<T>):Array<T>{
      return arr.map(item=>item)
    }
    
    formatArr<string>(['1', '2', '3'])
    formatArr<number>([1, 2, 3])
    

    定义函数 formatArr 时,在函数名后定义一个类型参数 <T>,同时参数数组项也接受 T 类型的值。
    在使用函数时,使用者传入的类型即为 T 的类型。

    尖括号内的变量名并不是固定的,可以自定义,一般都是大写

    泛型类

    泛型类实例化传入的泛型类型,可以在整个作用域中使用该泛型类型,但要注意的是类的静态属性无法使用泛型类型

    class Handsome<T>{
      static myname:T = 'Jack'  // 静态成员不能引用类类型参数
      girlfriend: Array<T>
      
      constructor(){
        this.girlfriend = []
      }
    
      addGirlfriend(name:T){
        this.girlfriend.push(name)
      }
    
      getAllGirlfriend():Array<T>{
        return this.girlfriend
      }
    }
    
    let handsome = new Handsome<string>()
    handsome.addGirlfriend('Julia')
    handsome.addGirlfriend('Vivian')
    //handsome.addGirlfriend({name:'Ellie'})  类型“{ name: string; }”的参数不能赋给类型“string”的参数
    handsome.getAllGirlfriend() // ['Julia', 'Vivian']
    

    泛型接口

    使用含有泛型的接口来定义函数

    interface CreateArrayFunc {
        <T>(length: number, value: T): Array<T>;
    }
    
    let createArray: CreateArrayFunc;
    createArray = function<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray(3, 'x'); // ['x', 'x', 'x']
    

    泛型参数提前到接口名

    interface CreateArrayFunc<T> {
        (length: number, value: T): Array<T>;
    }
    
    let createArray: CreateArrayFunc<string>;
    createArray = function<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray(3, 'x'); // ['x', 'x', 'x']
    

    这种方式使用泛型接口的时候,需要定义泛型的类型

    接口范围内的泛型

    看到上面这两种方式定义函数,使用起来差不多,可能你会有个疑问,它们有什么区别,哪种更好?(我第一次看到的时候也会有这个疑问)
    接下来,我用第二种方式再写一个例子,看看它们的区别在哪里。

    interface People<T>{
       name: T;
       friends: Array<T>;
       say: (msg:T)=>T
    }
    let student:People<string> = {
        name: 'Joe',
        friends: ['Mike','James','LuLu'],
        say: function(msg){
            return this.name+':'+msg
        }
    }
    

    就像泛型类一样,当你需要在接口范围内多次用到泛型参数时,可以将它提前到接口名。

    总的来说,以这种方式定义接口,可以统一接口内的类型,控制内部多个属性的参数类型。是不是这种就更好?不一定,如果你的需求只会用到一次泛型参数时,那就不必把泛型参数提前到接口名,因为在多人协同合作中,可能会引起其他使用者的误会。

    泛型约束

    泛型约束提供更智能的类型推导,为类型提供扩展。有时候我们希望泛型参数符合某些规则时,你应该想到使用泛型约束来解决问题。

    基于接口约束

    使用泛型约束来对 formatArr 做一些改造,改造后的函数功能为对传入的参数进行切片,返回除第一项的数据。但并不是每个类型都有 slice 方法,这时候就需要对泛型进行约束,规定只有 slice 方法的参数才可以传入。为此,定义一个含有 slice 方法的接口,使用这个接口和 extends 关键词实现约束。

    interface Slice{
      slice:Function;
    }
    function formatArr<T extends Slice>(arg:T):T{
      return arg.slice(1)
    }
    formatArr([1,2,3])
    formatArr('hello')
    

    keyof约束

    再来看一个泛型约束的例子,为函数定义两个泛型类型,T类型为对象,keyof定义U类型为T类型上的一个key值。在使用该函数,ts会进行类型推导,提示你第二个参数应该为第一个参数上的key值。

    function getValue<T extends object, U extends keyof T>(obj:T, key:U){
      return obj[key]
    }
    let obj = {
      color: 'white',
      size: 'big'
    }
    getValue(obj, "color") //ok
    getValue(obj, "name") //error  类型“"name"”的参数不能赋给类型“"color" | "size"”的参数
    

    上面的例子可能你会想到接口继承,因为接口继承也使用了 extends 关键词,要注意在泛型约束里extends并不是表示继承关系。

    泛型参数的默认类型

    在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。就像 ES6 中的函数默认参数一样,为代码增加健壮性。

    function createArray<T = string>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    

    写在最后

    通过以上使用泛型的几个例子,不难发现泛型的强大,可变的类型变量和泛型约束为 TypeScript 的类型推导都提供了很大的贡献。开发者根据类型提示能轻松知道怎么调用其他开发者封装的方法,像是基于文档编程的感觉,这也是为什么我们说在多人开发中,TypeScript 可以提高开发效率。类型是 TypeScript 的核心,也是它的魅力所在。理解并应用泛型,可以使我们的 TypeScript 水平更上一层楼。

  • 相关阅读:
    记druid 在配置中心下的一个大坑: cpu 达到 100%
    常见java日志系统的搭配详解:关于slf4j log4j log4j2 logback jul jcl commons-logging jdk-logging
    HTML一片空白, 无法渲染: Empty tag doesn't work in some browsers
    再见:org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
    spring boot tomcat 打本地包成war,通过Tomcat启动时出现问题: ZipException: error in opening zip file
    Maven 错误:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project appservice-common: Fatal error compiling: 无效的目标发行版: 1.8
    Maven 错误 :The POM for com.xxx:jar:0.0.1-SNAPSHOT is invalid, transitive dependencies (if any) will not be available
    LocalVariableTable之 Slot 复用
    一些常见的Java面试题 & 面试感悟
    spring 2.5.6 错误:Context namespace element 'component-scan' and its parser class [org.springframework.context.annotation.ComponentScanBeanDefinitionParser] are only available on JDK 1.5 and higher
  • 原文地址:https://www.cnblogs.com/chanwahfung/p/11965469.html
Copyright © 2020-2023  润新知