• 【Ts】约束对象、函数的正确姿势


    约束对象

    • 一般使用 [字面量] 约束变量的类型为对象
    let obj1: {}; // 约束该变量的类型为对象
    let obj2: { name: string }; // 约束该变量的类型为 [有且只有 name 属性的] 对象, 且 name 的属性值的类型必须是 string
    
    • 1
    • 2
    • 可以直接给变量赋值,TS 会自动识别类型,并对变量进行约束
    let obj = { width: 200 }; // 此时 obj 的类型被约束为 { width: number }
    
    • 1

    可选属性

    • 我们可以使用 ? 约束对象的指定属性是否可选
    let obj: { name: string; age?: number }; // age 属性可选
    
    • 1
    • 在使用可选属性时,需要在其后面也添加上 ?
      此时 如果属性值为 undefined,TS 会自动进行处理
    let obj: { name?: string };
    obj = { name: 'superman' };
    console.log(obj.name?.toLocaleUpperCase()); // SUPERMAN
    obj = {};
    console.log(obj.name?.toLocaleUpperCase()); // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其他属性

    • 可以设置 [自定义属性名: string]: 指定类型 接收其他的属性
      string - 约束 [属性名] 的类型为 string
      [] 括住 - 表示 [其他属性] 可以有 0 ~ n 个
    • 指定类型 - 需要兼容所有属性的类型
    type Person = {
        name: string;
        say(): string; // say 函数; 返回值的类型为 string
        [propName: string]: string | number; // 如果不确定 [其他属性] 的类型,可以约束为 any 类型
    }; // 这种写法表示 name、age 属性必填, 其他属性可选
    let obj: Person = { name: 'superman', age: 21, gender: 'male' };
    console.log('obj', obj); // obj { name: 'superman', age: 21, gender: 'male' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 这种办法也可用于数组身上:[自定义索引名: number]: 指定类型
    type MyArray1 = { [index: number]: string };
    type MyArray2 = string[]; // MyArray1 与 MyArray2 等效
    
    let arr1: MyArray1 = ["a", "b", "c"];
    let arr2: MyArray2 = ["a", "b", "c"];
    
    • 1
    • 2
    • 3
    • 4
    • 5

    只读属性

    • 只读属性:用 readonly 修饰的属性;只能读取、不能修改
    type ReadonlyPerson = {
        readonly age: number;
    };
    let readonlyPerson: ReadonlyPerson = { age: 20 };
    // readonlyPerson.age++; // 直接飘红
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 注意:如果 [只读属性] 的属性值为对象,那么这个对象的属性还是可以被修改的

      如果希望这个对象的属性也是只读的,可以给该属性也设置 readonly

    type Person = {
        readonly name: string;
        age: number;
    };
    
    type Superman = {
        readonly friend: Person;
    };
    
    let person: Person = { name: 'monster', age: 20 };
    let superman: Superman = { friend: person };
    console.log(superman); // { friend: { name: 'monster', age: 20 } }
    
    superman.friend.age++;
    // superman.friend.name = "superwomen"; // 直接飘红
    console.log(superman); // { friend: { name: 'monster', age: 21 } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 注意:可以直接将 [对象数据] 赋值给 [只读属性的对象数据]
      此时可通过修改 [对象数据的属性值] 来修改 [只读属性的对象数据的属性值]
    type Person = {
        age: number;
    };
    
    type ReadonlyPerson = {
        readonly age: number;
    };
    
    let person: Person = { age: 20 };
    let readonlyPerson: ReadonlyPerson = person;
    console.log(readonlyPerson); // { age: 20 }
    
    person.age++;
    console.log(readonlyPerson); // { age: 21 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • readonly 也可以用来约束 [其他属性]
    type Person = {
        name: string;
        readonly [propName: string]: any;
    };
    let person: Person = { name: 'superman', age: 21 };
    // person.age++; // 直接飘红
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6



    约束函数

    • 如果使用 [函数表达式] 定义函数,能以 [箭头函数] 的形式约束函数的 [参数] 和 [返回值]
    let showSum: (num1: number, num2: number) => void; // 约束变量 fun 的类型为特定的函数
    showSum = function (n1, n2) {
        console.log(n1 + n2);
    };
    
    • 1
    • 2
    • 3
    • 4

    num1num2 - 形参名,可随便自定义;表示 [有且只有 2 个参数]
    number - 约束 [参数] 的类型为 number
    void - 约束 [返回值] 的类型为 undefined

    • 注意:函数定义其返回值类型为 void 时,该函数不能返回除 undefined 外的其他值

      但是,上下文函数类型定义其返回值类型为 void 时 (type VoidFun = () => void)
      该上下文函数可以返回任意值,但是返回值的类型还是会被约束成 void

    // 普通函数写法
    function fun1(): void {
        // return true; // 直接飘红
    }
    
    // 函数表达式写法 (匿名函数)
    let fun2 = function (): void {
        // return true; // 直接飘红
    };
    
    // 上下文函数类型定义
    type VoidFun = () => void;
    let fun3: VoidFun = () => true; // 返回 true
    let res = fun3(); // res 被约束为 void 类型
    console.log('res', res); // res true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可选参数

    • 如果当前参数是可选参数,需要在该参数后面加上 ?
    • 使用可选参数时,也需要带上 ?
      此时 如果参数没有值,TS 会自动处理
    • 可选参数只能放在参数列表的后面
    function formatNum(num?: number) {
        console.log(num?.toFixed());
        console.log(num?.toFixed(1));
    }
    
    formatNum(123.456); // 123    123.5
    formatNum(); // undefined    undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用可选参数时,也可以在参数后面添加 !,表示你确保该参数一定会被传入:
    function showName(person?: { name: string }) {
        console.log("name", person!.name); // 表示参数 person 一定会被传入
    }
    
    showName({ name: "superman" });
    showName(); // 此时 执行代码会报错,因为没有传入参数 person
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    剩余参数

    • 可以设置 ...自定义形参名: 指定类型[] 接收剩余的参数
    let buildName = (firstName: string, ...restOfName: string[]) =>
        // 如果不确定剩余参数的类型,可以约束为 any[] 类型
        // 此时 restOfName 会以数组的形式存储剩余的所有参数
        `${firstName}-${restOfName.join('-')}`;
    
    console.log(buildName('Huang', 'Shi', 'Jie')); // Huang-Shi-Jie
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 注意:默认情况下 ... 修饰的都是数组,有时这会出问题
    const args = [1, 2];
    const sum = (num1: number, num2: number): number => num1 + num2;
    // console.log(sum(...args)); // 直接飘红
    // 因为 sum 只接收两个参数,而 args 自动被 TS 约束为数组,数组可能不只两个元素
    
    • 1
    • 2
    • 3
    • 4

    解决办法 ①:显式约束 args 为 [有且只有 2 个元素的数组],即 [元组] 类型
    解决办法 ②:使用 as constargs 约束为当前 [字面量]

    const args: [number, number] = [1, 2];
    const sum = (num1: number, num2: number): number => num1 + num2;
    console.log(sum(...args));
    
    • 1
    • 2
    • 3
    const args = [1, 2] as const;
    const sum = (num1: number, num2: number): number => num1 + num2;
    console.log(sum(...args));
    
    • 1
    • 2
    • 3

    调用签名

    • 如果需要给函数设置属性,可以通过 [调用签名] 约束函数的类型:
    // 注意:类型是对象的形式
    type FunType = {
        description: string; // 约束函数的 [属性]
        (someArg: number): boolean; // 调用签名 - 约束函数的 [参数] & [返回值]
    }; // 注意, 这里用的是 `(XX: XXX): XXX`,不是 `[XX: XXX]: XXX`
    
    function fun(num: number): boolean {
        return !!num;
    }
    fun.description = 'my function';
    
    function showFun(fn: FunType, num: number) {
        console.log(`${fn.description} return ${fn(num)}`);
    }
    showFun(fun, 0); // my function return false
    showFun(fun, 1); // my function return true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    构造签名

    • 其实就是在 [调用签名] 前面加 new 关键字
    // 定义一个类
    class Person {
        name: string;
        constructor(name: string) {
            this.name = name;
        }
    }
    
    // 注意:类型是对象的形式
    type FunType = {
        new (s: string): Person; // 构造签名 - 约束构造函数的 [参数] & [返回值]
    };
    
    function createPerson(ctor: FunType) {
        return new ctor('superman');
    }
    
    console.log(createPerson(Person)); // Person { name: 'superman' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    函数重载

    • 重载是方法名字相同,而参数不同;返回类型可以相同也可以不同
    • declare 用于声明函数,而不实现函数
    // 参数类型不同
    declare function fun(a: string): void;
    declare function fun(a: number): void;
    
    // 参数数量不同
    declare function fun(n: number): void;
    declare function fun(n: number, y: number): void;
    
    // 参数类型的顺序不同
    declare function fun(n: number, s: string): void;
    declare function fun(s: string, n: number): void;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 关于函数重载,必须把精确的 [重载函数] 写在前面,然后再写 [实现函数]

    • [实现函数] 的参数一定要兼容所有 [重载函数] 的参数

    • 调用函数时,要按照 [重载函数] 的约束去使用

    /* 声明 (重载函数) */
    function add(arg1: string, arg2: string): string;
    function add(arg1: number, arg2: number): number;
    // 因为在下边有具体的函数实现,所以不需要添加 declare 关键字
    
    /* 实现 (实现函数) */
    function add(arg1: number | string, arg2: string | number): number | string {
        if (typeof arg1 === 'number' && typeof arg2 === 'number') {
            console.log('进行数值相加');
            return arg1 + arg2;
        } else {
            console.log('进行字符串拼接');
            return (arg1 as string) + (arg2 as string);
        }
    }
    
    let addResult1 = add(10, 20); // addResult1 的类型为 number
    let addResult2 = add('10', '20'); // addResult2 的类型为 string
    // let addResult3 = add("10", 20); // 直接飘红,因为 [重载函数] 中没有允许这种类型的参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    /* 声明 (重载函数) */
    function makeDate(timeStamp: number): Date;
    function makeDate(m: number, d: number, y: number): Date;
    
    /* 实现 (实现函数) */
    function makeDate(timeStampOrM: number, d?: number, y?: number): Date {
        if (d !== undefined && y !== undefined) {
            return new Date(y, timeStampOrM, d);
        } else {
            return new Date(timeStampOrM);
        }
    }
    
    let addResult1 = makeDate(1661044053502);
    let addResult2 = makeDate(8, 31, 2000);
    // let addResult3 = makeDate(8, 31); // 直接飘红,[重载函数] 中没有允许这种类型的参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 在某些情况下,我们更偏向于使用联合类型 | 而不使用函数重载
    /* 使用函数重载 */
    function getLength(x: any[]): number;
    function getLength(x: string): number;
    
    function getLength(x: any): number {
        return x.length;
    }
    
    // let addResult1 = getLength(Math.random() < 0.5 ? "superman" : [1, 2, 3]); // 直接飘红
    // 因为 "大" 类型不能赋值给 "小" 类型
    // 而参数的类型 `"superman" | number[]` 比 `any[]` 和 `string` 都要 "大"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    /* 使用联合类型 `|` */
    function getLength(x: any[] | string): number {
        return x.length;
    }
    
    let addResult1 = getLength(Math.random() < 0.5 ? 'superman' : [1, 2, 3]); // 可以正常使用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    关于对象类型的参数

    • 函数接收对象类型的参数时,可以使用 [解构赋值]
      使用 [解构赋值] 时需要注意:ES6 中 [属性别名] 的写法与 TS 中 [约束类型] 的写法会冲突
      此时,生效的是 ES6 中的 [属性别名]
    interface Person {
        name: string;
        age?: number;
    }
    
    function showPerson({ name: string, age: number = 0 }: Person) {
        // 使用解构赋值接收对象参数
        // 此时,string、number 不是用于约束类型,而是属性的别名
        console.log('name', string); // name superman
        console.log('age', number); // age 0
    }
    
    showPerson({ name: 'superman' });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    基于SpringBoot的二手商品交易平台
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    【Python程序设计】基于Flask的音乐在线网站/系统/平台
    Qt moc: Too many input files specified
    Qt设计一个自定义的登录框窗口
    构建基于深度学习神经网络协同过滤模型(NCF)的视频推荐系统(Python3.10/Tensorflow2.11)
    2023/09/19 qt day3
    将docker打包成镜像并保存到本地
    关于小程序session_key漏洞问题的解决2022-12-01
    实心轮胎的优缺点
  • 原文地址:https://blog.csdn.net/Superman_H/article/details/126455644