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

约束对象

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

可选属性

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

其他属性

  • 可以设置 [自定义属性名: 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' }
  • 这种办法也可用于数组身上:[自定义索引名: number]: 指定类型
type MyArray1 = {
    
     [index: number]: string };
type MyArray2 = string[]; // MyArray1 与 MyArray2 等效

let arr1: MyArray1 = ["a", "b", "c"];
let arr2: MyArray2 = ["a", "b", "c"];

只读属性

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

    如果希望这个对象的属性也是只读的,可以给该属性也设置 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 } }
  • 注意:可以直接将 [对象数据] 赋值给 [只读属性的对象数据]
    此时可通过修改 [对象数据的属性值] 来修改 [只读属性的对象数据的属性值]
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 }
  • readonly 也可以用来约束 [其他属性]
type Person = {
    
    
    name: string;
    readonly [propName: string]: any;
};
let person: Person = {
    
     name: 'superman', age: 21 };
// person.age++; // 直接飘红



约束函数

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

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

可选参数

  • 如果当前参数是可选参数,需要在该参数后面加上 ?
  • 使用可选参数时,也需要带上 ?
    此时 如果参数没有值,TS 会自动处理
  • 可选参数只能放在参数列表的后面
function formatNum(num?: number) {
    
    
    console.log(num?.toFixed());
    console.log(num?.toFixed(1));
}

formatNum(123.456); // 123    123.5
formatNum(); // undefined    undefined
  • 使用可选参数时,也可以在参数后面添加 !,表示你确保该参数一定会被传入:
function showName(person?: {
     
      name: string }) {
    
    
    console.log("name", person!.name); // 表示参数 person 一定会被传入
}

showName({
    
     name: "superman" });
showName(); // 此时 执行代码会报错,因为没有传入参数 person

剩余参数

  • 可以设置 ...自定义形参名: 指定类型[] 接收剩余的参数
let buildName = (firstName: string, ...restOfName: string[]) =>
    // 如果不确定剩余参数的类型,可以约束为 any[] 类型
    // 此时 restOfName 会以数组的形式存储剩余的所有参数
    `${
      
      firstName}-${
      
      restOfName.join('-')}`;

console.log(buildName('Huang', 'Shi', 'Jie')); // Huang-Shi-Jie
  • 注意:默认情况下 ... 修饰的都是数组,有时这会出问题
const args = [1, 2];
const sum = (num1: number, num2: number): number => num1 + num2;
// console.log(sum(...args)); // 直接飘红
// 因为 sum 只接收两个参数,而 args 自动被 TS 约束为数组,数组可能不只两个元素

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

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

调用签名

  • 如果需要给函数设置属性,可以通过 [调用签名] 约束函数的类型:
// 注意:类型是对象的形式
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

构造签名

  • 其实就是在 [调用签名] 前面加 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' }

函数重载

  • 重载是方法名字相同,而参数不同;返回类型可以相同也可以不同
  • 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;
  • 关于函数重载,必须把精确的 [重载函数] 写在前面,然后再写 [实现函数]

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

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

/* 声明 (重载函数) */
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); // 直接飘红,因为 [重载函数] 中没有允许这种类型的参数
/* 声明 (重载函数) */
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); // 直接飘红,[重载函数] 中没有允许这种类型的参数
  • 在某些情况下,我们更偏向于使用联合类型 | 而不使用函数重载
/* 使用函数重载 */
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` 都要 "大"
/* 使用联合类型 `|` */
function getLength(x: any[] | string): number {
    
    
    return x.length;
}

let addResult1 = getLength(Math.random() < 0.5 ? 'superman' : [1, 2, 3]); // 可以正常使用

关于对象类型的参数

  • 函数接收对象类型的参数时,可以使用 [解构赋值]
    使用 [解构赋值] 时需要注意: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' });

猜你喜欢

转载自blog.csdn.net/Superman_H/article/details/126455644