Skip to content

属性修改器

在 JavaScript 中,我们分组和传递数据的基本方式是通过对象。在 TypeScript 中,我们通过对象类型来表示这些对象。 正如我们所见,它们可以是匿名的:

typescript
function greet(person: { name: string; age: number }) {
  return "Hello " + person.name;
}

或者可以通过使用一个接口来命名它们:

typescript
interface Person {
  name: string;
  age: number;
}

function greet(person: Person) {
  return "Hello " + person.name;
}

或一个类型别名:

typescript
type Person = {
  name: string;
  age: number;
};

function greet(person: Person) {
  return "Hello " + person.name;
}

在上面的三个例子中,我们写了一些函数,这些函数接收包含属性 name (必须是一个 string )和 age (必须是一个 number)的对象。

对象类型中的每个属性都可以指定几件事:类型、属性是否是可选的,以及属性是否可以被写入。

可选属性

很多时候,我们会发现自己处理的对象可能有一个属性设置。在这些情况下,我们可以在这些属性的名字后面加上一个问号(?),把它们标记为可选的。

typescript
type Shape = {};

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}

function paintShape(opts: PaintOptions) {
  // ...
}

const shape: Shape = {};
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });

在这个例子中, xPos 和 yPos 都被认为是可选的。我们可以选择提供它们中的任何一个,所以上面对 paintShape 的每个调用都是有效的。所有的可选性实际上是说,如果属性被设置,它最好有一个特定的类型。

我们也可以从这些属性中读取,但当我们在 strictNullChecks 下读取时,TypeScript 会告诉我们它们可能是未定义的。

typescript
function paintShape(opts: PaintOptions) {
  let xPos = opts.xPos;
  let yPos = opts.yPos;
  // ...
}

在 JavaScript 中,即使该属性从未被设置过,我们仍然可以访问它--它只是会给我们未定义的值。我们可以专门处理未定义。

typescript
function paintShape(opts: PaintOptions) {
  let xPos = opts.xPos === undefined ? 0 : opts.xPos;
  let yPos = opts.yPos === undefined ? 0 : opts.yPos;
  // ...
}

请注意,这种为未指定的值设置默认值的模式非常普遍,以至于 JavaScript 有语法来支持它。

typescript
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
  console.log("x coordinate at", xPos);
  console.log("y coordinate at", yPos);
  // ...
}

在这里,我们为 paintShape 的参数使用了一个解构模式,并为 xPos 和 yPos 提供了默认值。现在 xPos 和 yPos 都肯定存在于 paintShape 的主体中,但对于 paintShape 的任何调用者来说是可选的。

请注意,目前还没有办法将类型注释放在解构模式中。这是因为下面的语法在 JavaScript 中已经有了不同的含义。

typescript
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
  render(shape);
  render(xPos);
}

在一个对象解构模式中, shape: Shape 意味着 "获取属性 shape ,并在本地重新定义为一个名为 Shape 的变量。同样, xPos: number 创建一个名为 number 的变量,其值基于参数的 xPos。

只读属性

对于 TypeScript ,属性也可以被标记为只读。虽然它不会在运行时改变任何行为,但在类型检查期间,一个标记为只读的属性不能被写入。

typescript
interface SomeType {
  readonly prop: string;
}

function doSomething(obj: SomeType) {
  // 可以读取 'obj.prop'.
  console.log(`prop has the value '${obj.prop}'.`);
  // 但不能重新设置值
  obj.prop = "hello";
}

使用 readonly 修饰符并不一定意味着一个值是完全不可改变的。或者换句话说,它的内部内容不能被改变,它只是意味着该属性本身不能被重新写入。

typescript
interface Home {
  readonly resident: { name: string; age: number };
}

function visitForBirthday(home: Home) {
  // 我们可以从'home.resident'读取和更新属性。
  console.log(`Happy birthday ${home.resident.name}!`);
  home.resident.age++;
}

function evict(home: Home) {
  // 但是我们不能写到'home'上的'resident'属性本身。
  home.resident = {
    name: "Victor the Evictor",
    age: 42,
  };
}

管理对 readonly 含义的预期是很重要的。

在 TypeScript 的开发过程中,对于一个对象应该如何被使用的问题,它是有用的信号。TypeScript 在检查两个类型的属性是否兼容时,并不考虑这些类型的属性是否是 readonly ,所以 readony 属性也可以通过别名来改变。

typescript
interface Person {
  name: string;
  age: number;
}

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

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

// 正常工作
let readonlyPerson: ReadonlyPerson = writablePerson;

console.log(readonlyPerson.age); // 打印 '42'
writablePerson.age++;
console.log(readonlyPerson.age); // 打印 '43'

索引签名

有时你并不提前知道一个类型的所有属性名称,但你知道值的形状。 在这些情况下,你可以使用一个索引签名来描述可能的值的类型,比如说:

typescript
interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = ["a", "b"];
const secondItem = myArray[1];

上面,我们有一个 StringArray 接口,它有一个索引签名。这个索引签名指出,当一个 StringArray 被数字索引时,它将返回一个字符串。

索引签名的属性类型必须是 string 或 number 。

支持两种类型的索引器是可能的,但是从数字索引器返回的类型必须是字符串索引器返回的类型的子类型。这是因为当用 "数字 "进行索引时,JavaScript 实际上会在索引到一个对象之前将其转换为 "字符串"。这意味着用 100 (一个数字)进行索引和用 "100" (一个字符串 )进行索引是一样的,所以两者需要一致。

typescript
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

interface NotOkay {
  [x: number]: Animal;
  [x: string]: Dog;
}

虽然字符串索引签名是描述 "字典 "模式的一种强大方式,但它也强制要求所有的属性与它们的返回类型相匹配。这是因为字符串索引声明 obj.property 也可以作为 obj["property"] 。

在下面的例子中,name 的类型与字符串索引的类型不匹配,类型检查器会给出一个错误:

typescript
interface NumberDictionary {
  [index: string]: number;

  length: number; // ok
  name: string;
}

然而,如果索引签名是属性类型的联合,不同类型的属性是可以接受的:

typescript
interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; // 正确, length 是 number 类型
  name: string; // 正确, name 是 string 类型
}

最后,你可以使索引签名为只读,以防止对其索引的赋值:

typescript
interface ReadonlyStringArray {
  readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";

你不能设置 myArray[2] ,因为这个索引签名是只读的。