TypeScript$Type-Value-Object

65 阅读3分钟

TypeScript$Type-Value-Object

1.0 object, {} and Object

object

object 表示对象类型

Object

object 类似,Object 也表示对象类型,但是有些怪异行为。比如一个对象重写了 toString 方法,如果重写的方法返回了非 string 类型的值,则会报错。所以基本不用 Object

{}

{} 表示除了 undefinednull 的类型。

1.1 Define Object Types - Basic

对于对象类型 object type,需要描述对象的各个属性及其类型:

  • 可选属性:?:
  • 只读属性:readonly(不能被重新赋值)
    • readonly 只对声明的变量起作用,不考虑类型的兼容性——如果一个非 readonly 的变量 n 赋值给了 readonly 的变量 r,那么 r readonly,但 n 不是。
    • Using mapping modifiers, you can remove readonly attributes.(将在Type-Manipulation-MappedType 里说明)
  • index signatures:不知道 property names,但是知道 property types (label's types and value's types)(label 指的是 property name,我更倾向于 label-value 组合)
    • index signature property label 允许的类型:stringnumbersymbol, template string patterns, and union types consisting only of these
    • index signature property label 的类型可以有多种。需要注意的是,如果 index signature property label 的类型是 number,那么 index signature property values 需要是 string 时的子集。(因为 number => string(JavaScript 的知识))
    • index signatures 和其他正常的属性有约束:如果其他的属性的返回值不符合 index signatures,会报错(因为有矛盾)
type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

Optional Properties

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}
 
function paintShape(opts: PaintOptions) {
  // ...
}
 
const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });

readonly Properties

interface SomeType {
  readonly prop: string;
}
 
function doSomething(obj: SomeType) {
  // We can read from 'obj.prop'.
  console.log(`prop has the value '${obj.prop}'.`);
 
  // But we can't re-assign it.
  obj.prop = "hello";
// error, Cannot assign to 'prop' because it is a read-only property.
}
interface Person {
  name: string;
  age: number;
}

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};

// works
let readonlyPerson: ReadonlyPerson = writablePerson;

console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'
readonlyPerson.age++ // error

Index Signatures

interface StringArray {
  [index: number]: string;
}
 
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
interface Animal {
  name: string;
}
 
interface Dog extends Animal {
  breed: string;
}
 
// error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
  [x: number]: Animal;
// 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Dog;
}
interface NumberDictionary {
  [index: string]: number;
 
  length: number; // ok
  name: string;
// error, Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
interface ReadonlyStringArray {
  readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
// error, Index signature in type 'ReadonlyStringArray' only permits reading.

Use case: the “type registry” pattern

module declaration. it allows us to effectively layer types on top of things already exported by a module ./lib/registry.ts.

declare module "./lib/registry" {
}
data/
  book.ts       // A model for Book records
  magazine.ts   // A model for Magazine records
lib/
  registry.ts   // Our type registry, and a `fetchRecord` function
index.ts        // Entry point
// Assumption -- our user has set up resources like Book and Magazine
//
// returns a Book
fetchRecord("book", "bk_123")
// returns a Magazine
fetchRecord("magazine", "mz_456")
// maybe should refuse to compile
fetchRecord("blah", "")
// @filename: lib/registry.ts
export interface DataTypeRegistry
{
 // empty by design
}
// the "& string" is just a trick to get
// a nicer tooltip to show you in the next step
export function fetchRecord(arg: keyof DataTypeRegistry & string, id: string) {
}
// @filename: data/book.ts
export class Book {
  deweyDecimalNumber(): number {
    return 42
  }
}
declare module "../lib/registry" {
  export interface DataTypeRegistry {
    book: Book
  }
}
 
 
// @filename: data/magazine.ts
export class Magazine {
  issueNumber(): number {
    return 42
  }
}
 
declare module "../lib/registry" {
  export interface DataTypeRegistry {
    magazine: Magazine
  }
}
// @filename: index.ts
import { DataTypeRegistry, fetchRecord } from './lib/registry'

fetchRecord("book", "bk_123")
//  ^?
// (alias) fetchRecord(arg: "book" | "magazine", id: string): void import fetchRecord

satisfies

type DateLike = Date | number | string;
 
type Holidays = {
  [k: string]: DateLike
}
 
const usaHolidays = {
  independenceDay: "July 4, 2024",
  memorialDay: new Date("May 27, 2024"),
  laborDay: 1725260400000, // September 2, 2024
} satisfies Holidays
 
usaHolidays
    /* const usaHolidays: {
    independenceDay: string;
    memorialDay: Date;
    laborDay: number;
} */

1.2 Extending Types extends interface

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
  unit: string;
}
interface Colorful {
  color: string;
}
 
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

1.3 Intersection Types & interface

属性都有才可以

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
 
type ColorfulCircle = Colorful & Circle;

2. Excess Property Checks

在使用时,对象字面量 literal objects 不允许有多余的属性,这是为了防止拼写错误。

interface SquareConfig {
  color?: string;
  width?: number;
}
 
function createSquare(config: SquareConfig): { color: string; area: number } {
  return {
    color: config.color || "red",
    area: config.width ? config.width * config.width : 20,
  };
}
 
let mySquare = createSquare({ colour: "red", width: 100 });
// error, Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?

3. Generic Object Types

有时候我们知道一个对象的 property names,但是不知道 property values。比如我们知道一个盒子 Box,里面的内容是 contents,但是 contents 的类型不知道:可能 Box 装的是 string | number | ... 如何表示 contents 的类型呢?我们可以假设类型是 T,当具体生成对象的时候,这个 T 就确认了。 T 就是个占位符 placeholder。这个 Box 就像是模板 template,是用来产生其他具体类型的。

用官方文档中的话来说:

generic Box type which declares a type parameter.

When we refer to Box, we have to give a type argument in place of Type.

interface Box<Type> {
  contents: Type;
}

let box: Box<string>;

Helper Types

type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
 
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
           // type OneOrManyOrNull<Type> = OneOrMany<Type> | null
 
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
               // type OneOrManyOrNullStrings = OneOrMany<string> | null

Links

TypeScriptObjectTypes