返回

谈谈 TypeScript 的奇妙用法

前端

在 TypeScript 中,keyof 操作符可以获取类型的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后使用 T[P] 取得相应属性的值。但是,这只能适用于一层嵌套的情况,如上面的 Partial 例子来看,jack.person.name 是可以直接修改的。

上面的 - ?很好理解,就是将可选项代表的 ? 去掉,从而将属性声明为必选。Pick<T, K> 是从 T 中挑选出 K 所指定的属性,组成一个新的类型。Omit<T, K> 是从 T 中排除 K 所指定的属性,组成一个新的类型。

让我们深入探索这些类型操作的奇妙用法:

  1. 获取对象的所有属性名:

    interface Person {
        name: string;
        age: number;
        city: string;
    }
    
    type PersonKeys = keyof Person; // "name" | "age" | "city"
    

    现在,我们可以使用 PersonKeys 来遍历 Person 对象的所有属性:

    function logPersonKeys(person: Person) {
        for (const key of Object.keys(person) as PersonKeys) {
            console.log(key, person[key]);
        }
    }
    
    logPersonKeys({ name: "Alice", age: 25, city: "London" });
    // "name" "Alice"
    // "age" 25
    // "city" "London"
    
  2. 根据条件筛选属性:

    我们可以使用条件类型来根据条件筛选属性:

    type NameOrAge<T> = T extends { name: string; age: number } ? "name" | "age" : never;
    
    interface Person {
        name: string;
        age: number;
        city: string;
    }
    
    type PersonNameOrAge = NameOrAge<Person>; // "name" | "age"
    

    现在,我们可以使用 PersonNameOrAge 来挑选出 Person 对象中 nameage 这两个属性:

    function logPersonNameOrAge(person: Person) {
        for (const key of Object.keys(person) as PersonNameOrAge) {
            console.log(key, person[key]);
        }
    }
    
    logPersonNameOrAge({ name: "Alice", age: 25, city: "London" });
    // "name" "Alice"
    // "age" 25
    
  3. 创建映射类型:

    我们可以使用映射类型来创建新的类型,其中每个属性的值都是根据原类型中的属性计算得来的:

    type Double<T> = {
        [K in keyof T]: T[K] extends number ? T[K] * 2 : T[K];
    };
    
    interface Person {
        name: string;
        age: number;
        city: string;
    }
    
    type DoubledPerson = Double<Person>; // { name: string; age: number * 2; city: string; }
    

    现在,我们可以使用 DoubledPerson 来创建一个新的对象,其中 age 属性的值是原来的两倍:

    const doubledPerson: DoubledPerson = {
        name: "Alice",
        age: 25,
        city: "London",
    };
    
    console.log(doubledPerson);
    // { name: "Alice", age: 50, city: "London" }
    
  4. 创建索引类型:

    我们可以使用索引类型来创建新的类型,其中属性的名称是动态生成的:

    type Indexable<T> = {
        [K in keyof T]: T[K];
    };
    
    interface Person {
        name: string;
        age: number;
        city: string;
    }
    
    type IndexablePerson = Indexable<Person>; // { [K in keyof Person]: Person[K]; }
    

    现在,我们可以使用 IndexablePerson 来创建一个新的对象,其中属性的名称是 Person 对象中属性的名称:

    const indexablePerson: IndexablePerson = {
        name: "Alice",
        age: 25,
        city: "London",
    };
    
    console.log(indexablePerson.name); // "Alice"
    console.log(indexablePerson.age); // 25
    console.log(indexablePerson.city); // "London"
    

这些只是 TypeScript 中类型操作的众多奇妙用法的几个例子。通过熟练掌握这些类型操作,我们可以编写出更灵活、更强大的代码。