Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript 之 Object Types #221

Open
mqyqingfeng opened this issue Nov 18, 2021 · 28 comments
Open

TypeScript 之 Object Types #221

mqyqingfeng opened this issue Nov 18, 2021 · 28 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Nov 18, 2021

前言

TypeScript 的官方文档早已更新,但我能找到的中文文档都还停留在比较老的版本。所以对其中新增以及修订较多的一些章节进行了翻译整理。

本篇整理自 TypeScript Handbook 中 「Object Types」 章节。

本文并不严格按照原文翻译,对部分内容也做了解释补充。

对象类型(Object types)

在 JavaScript 中,最基本的将数据成组和分发的方式就是通过对象。在 TypeScript 中,我们通过对象类型(object types)来描述对象。

对象类型可以是匿名的:

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

也可以使用接口进行定义:

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

或者通过类型别名:

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

属性修饰符(Property Modifiers)

对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息。

可选属性(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 });

在这个例子中,xPosyPos 就是可选属性。因为他们是可选的,所以上面所有的调用方式都是合法的。

我们也可以尝试读取这些属性,但如果我们是在 strictNullChecks 模式下,TypeScript 会提示我们,属性值可能是 undefined

function paintShape(opts: PaintOptions) {
  let xPos = opts.xPos;              
  // (property) PaintOptions.xPos?: number | undefined
  let yPos = opts.yPos;
  // (property) PaintOptions.yPos?: number | undefined
}

在 JavaScript 中,如果一个属性值没有被设置,我们获取会得到 undefined 。所以我们可以针对 undefined 特殊处理一下:

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

这种判断在 JavaScript 中很常见,以至于提供了专门的语法糖:

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

这里我们使用了解构语法以及为 xPosyPos 提供了默认值。现在 xPosyPos 的值在 paintShape 函数内部一定存在,但对于 paintShape 的调用者来说,却是可选的。

注意现在并没有在解构语法里放置类型注解的方式。这是因为在 JavaScript 中,下面的语法代表的意思完全不同。

function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
  render(shape);
  // Cannot find name 'shape'. Did you mean 'Shape'?
  render(xPos);
  // Cannot find name 'xPos'.
}

在对象解构语法中,shape: Shape 表示的是把 shape 的值赋值给局部变量 ShapexPos: number 也是一样,会基于 xPos 创建一个名为 number 的变量。

readonly 属性(readonly Properties)

在 TypeScript 中,属性可以被标记为 readonly,这不会改变任何运行时的行为,但在类型检查的时候,一个标记为 readonly的属性是不能被写入的。

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";
  // Cannot assign to 'prop' because it is a read-only property.
}

不过使用 readonly 并不意味着一个值就完全是不变的,亦或者说,内部的内容是不能变的。readonly 仅仅表明属性本身是不能被重新写入的。

interface Home {
  readonly resident: { name: string; age: number };
}
 
function visitForBirthday(home: Home) {
  // We can read and update properties from 'home.resident'.
  console.log(`Happy birthday ${home.resident.name}!`);
  home.resident.age++;
}
 
function evict(home: Home) {
  // But we can't write to the 'resident' property itself on a 'Home'.
  home.resident = {
  // Cannot assign to 'resident' because it is a read-only property.
    name: "Victor the Evictor",
    age: 42,
  };
}

TypeScript 在检查两个类型是否兼容的时候,并不会考虑两个类型里的属性是否是 readonly,这就意味着,readonly 的值是可以通过别名修改的。

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'

索引签名(Index Signatures)

有的时候,你不能提前知道一个类型里的所有属性的名字,但是你知道这些值的特征。

这种情况,你就可以用一个索引签名 (index signature) 来描述可能的值的类型,举个例子:

interface StringArray {
  [index: number]: string;
}
 
const myArray: StringArray = getStringArray();
const secondItem = myArray[1]; // const secondItem: string

这样,我们就有了一个具有索引签名的接口 StringArray,这个索引签名表示当一个 StringArray 类型的值使用 number 类型的值进行索引的时候,会返回一个 string类型的值。

一个索引签名的属性类型必须是 string 或者是 number

虽然 TypeScript 可以同时支持 stringnumber 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的。

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;
}

尽管字符串索引用来描述字典模式(dictionary pattern)非常的有效,但也会强制要求所有的属性要匹配索引签名的返回类型。这是因为一个声明类似于 obj.property 的字符串索引,跟 obj["property"]是一样的。在下面的例子中,name 的类型并不匹配字符串索引的类型,所以类型检查器会给出报错:

interface NumberDictionary {
  [index: string]: number;
 
  length: number; // ok
  name: string;
	// Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}

然而,如果一个索引签名是属性类型的联合,那各种类型的属性就可以接受了:

interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; // ok, length is a number
  name: string; // ok, name is a string
}

最后,你也可以设置索引签名为 readonly

interface ReadonlyStringArray {
  readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
// Index signature in type 'ReadonlyStringArray' only permits reading.

因为索引签名是 readonly ,所以你无法设置 myArray[2] 的值。

属性继承(Extending Types)

有时我们需要一个比其他类型更具体的类型。举个例子,假设我们有一个 BasicAddress 类型用来描述在美国邮寄信件和包裹的所需字段。

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

这在一些情况下已经满足了,但同一个地址的建筑往往还有不同的单元号,我们可以再写一个 AddressWithUnit

interface AddressWithUnit {
  name?: string;
  unit: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

这样写固然可以,但为了加一个字段,就是要完全的拷贝一遍。

我们可以改成继承 BasicAddress的方式来实现:

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
  unit: string;
}

对接口使用 extends关键字允许我们有效的从其他声明过的类型中拷贝成员,并且随意添加新成员。

接口也可以继承多个类型:

interface Colorful {
  color: string;
}
 
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

交叉类型(Intersection Types)

TypeScript 也提供了名为交叉类型(Intersection types)的方法,用于合并已经存在的对象类型。

交叉类型的定义需要用到 & 操作符:

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

这里,我们连结 ColorfulCircle 产生了一个新的类型,新类型拥有 ColorfulCircle 的所有成员。

function draw(circle: Colorful & Circle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}
 
// okay
draw({ color: "blue", radius: 42 });
 
// oops
draw({ color: "red", raidus: 42 });
// Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
// Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?

接口继承与交叉类型(Interfalces vs Intersections)

这两种方式在合并类型上看起来很相似,但实际上还是有很大的不同。最原则性的不同就是在于冲突怎么处理,这也是你决定选择那种方式的主要原因。

interface Colorful {
  color: string;
}

interface ColorfulSub extends Colorful {
  color: number
}

// Interface 'ColorfulSub' incorrectly extends interface 'Colorful'.
// Types of property 'color' are incompatible.
// Type 'number' is not assignable to type 'string'.

使用继承的方式,如果重写类型会导致编译错误,但交叉类型不会:

interface Colorful {
  color: string;
}

type ColorfulSub = Colorful & {
  color: number
}

虽然不会报错,那 color 属性的类型是什么呢,答案是 never,取得是 stringnumber 的交集。

泛型对象类型(Generic Object Types)

让我们写这样一个 Box 类型,可以包含任何值:

interface Box {
  contents: any;
}

现在 content 属性的类型为 any,可以用,但容易导致翻车。

我们也可以代替使用 unknown,但这也意味着,如果我们已经知道了 contents 的类型,我们需要做一些预防检查,或者用一个容易错误的类型断言。

interface Box {
  contents: unknown;
}
 
let x: Box = {
  contents: "hello world",
};
 
// we could check 'x.contents'
if (typeof x.contents === "string") {
  console.log(x.contents.toLowerCase());
}
 
// or we could use a type assertion
console.log((x.contents as string).toLowerCase());

一个更加安全的做法是将 Box 根据 contents 的类型拆分的更具体一些:

interface NumberBox {
  contents: number;
}
 
interface StringBox {
  contents: string;
}
 
interface BooleanBox {
  contents: boolean;
}

但是这也意味着我们不得不创建不同的函数或者函数重载处理不同的类型:

function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
  box.contents = newContents;
}

这样写就太繁琐了。

所以我们可以创建一个泛型 Box ,它声明了一个类型参数 (type parameter):

interface Box<Type> {
  contents: Type;
}

你可以这样理解:BoxType 就是 contents 拥有的类型 Type

当我们引用 Box 的时候,我们需要给予一个类型实参替换掉 Type

let box: Box<string>;

Box 想象成一个实际类型的模板,Type 就是一个占位符,可以被替代为具体的类型。当 TypeScript 看到 Box<string>,它就会替换为 Box<Type>Typestring ,最后的结果就会变成 { contents: string }。换句话说,Box<string>StringBox 是一样的。

interface Box<Type> {
  contents: Type;
}
interface StringBox {
  contents: string;
}
 
let boxA: Box<string> = { contents: "hello" };
boxA.contents;        
// (property) Box<string>.contents: string
 
let boxB: StringBox = { contents: "world" };
boxB.contents;     
// (property) StringBox.contents: string

不过现在的 Box 是可重复使用的,如果我们需要一个新的类型,我们完全不需要再重新声明一个类型。

interface Box<Type> {
  contents: Type;
}
 
interface Apple {
  // ....
}
 
// Same as '{ contents: Apple }'.
type AppleBox = Box<Apple>;

这也意味着我们可以利用泛型函数避免使用函数重载。

function setContents<Type>(box: Box<Type>, newContents: Type) {
  box.contents = newContents;
}

类型别名也是可以使用泛型的。比如:

interface Box<Type> {
  contents: Type;
}

使用别名对应就是:

type Box<Type> = {
  contents: Type;
};

类型别名不同于接口,可以描述的不止是对象类型,所以我们也可以用类型别名写一些其他种类的的泛型帮助类型。

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

Array 类型(The Array Type)

我们之前讲过 Array 类型,当我们这样写类型 number[] 或者 string[] 的时候,其实它们只是 Array<number>Array<string> 的简写形式而已。

function doSomething(value: Array<string>) {
  // ...
}
 
let myArray: string[] = ["hello", "world"];
 
// either of these work!
doSomething(myArray);
doSomething(new Array("hello", "world"));

类似于上面的 Box 类型,Array 本身就是一个泛型:

interface Array<Type> {
  /**
   * Gets or sets the length of the array.
   */
  length: number;
 
  /**
   * Removes the last element from an array and returns it.
   */
  pop(): Type | undefined;
 
  /**
   * Appends new elements to an array, and returns the new length of the array.
   */
  push(...items: Type[]): number;
 
  // ...
}

现代 JavaScript 也提供其他是泛型的数据结构,比如 Map<K, V>Set<T>Promise<T>。因为 MapSetPromise的行为表现,它们可以跟任何类型搭配使用。

ReadonlyArray 类型(The ReadonlyArray Type)

ReadonlyArray 是一个特殊类型,它可以描述数组不能被改变。

function doStuff(values: ReadonlyArray<string>) {
  // We can read from 'values'...
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ...but we can't mutate 'values'.
  values.push("hello!");
  // Property 'push' does not exist on type 'readonly string[]'.
}

ReadonlyArray 主要是用来做意图声明。当我们看到一个函数返回 ReadonlyArray,就是在告诉我们不能去更改其中的内容,当我们看到一个函数支持传入 ReadonlyArray ,这是在告诉我们我们可以放心的传入数组到函数中,而不用担心会改变数组的内容。

不像 ArrayReadonlyArray 并不是一个我们可以用的构造器函数。

new ReadonlyArray("red", "green", "blue");
// 'ReadonlyArray' only refers to a type, but is being used as a value here.

然而,我们可以直接把一个常规数组赋值给 ReadonlyArray

const roArray: ReadonlyArray<string> = ["red", "green", "blue"];

TypeScript 也针对 ReadonlyArray<Type> 提供了更简短的写法 readonly Type[]

function doStuff(values: readonly string[]) {
  // We can read from 'values'...
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ...but we can't mutate 'values'.
  values.push("hello!");
  // Property 'push' does not exist on type 'readonly string[]'.
}

最后有一点要注意,就是 ArraysReadonlyArray 并不能双向的赋值:

let x: readonly string[] = [];
let y: string[] = [];
 
x = y; // ok
y = x; // The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

元组类型(Tuple Types)

元组类型是另外一种 Array 类型,当你明确知道数组包含多少个元素,并且每个位置元素的类型都明确知道的时候,就适合使用元组类型。

type StringNumberPair = [string, number];

在这个例子中,StringNumberPair 就是 stringnumber 的元组类型。

ReadonlyArray 一样,它并不会在运行时产生影响,但是对 TypeScript 很有意义。因为对于类型系统,StringNumberPair 描述了一个数组,索引 0 的值的类型是 string,索引 1 的值的类型是 number

function doSomething(pair: [string, number]) {
  const a = pair[0];
       
const a: string
  const b = pair[1];
       
const b: number
  // ...
}
 
doSomething(["hello", 42]);

如果要获取元素数量之外的元素,TypeScript 会提示错误:

function doSomething(pair: [string, number]) {
  // ...
 
  const c = pair[2];
  // Tuple type '[string, number]' of length '2' has no element at index '2'.
}

我们也可以使用 JavaScript 的数组解构语法解构元组:

function doSomething(stringHash: [string, number]) {
  const [inputString, hash] = stringHash;
  console.log(inputString); // const inputString: string
  console.log(hash); // const hash: number
}

元组类型在重度依赖约定的 API 中很有用,因为它会让每个元素的意义都很明显。当我们解构的时候,元组给了我们命名变量的自由度。在上面的例子中,我们可以命名元素 01 为我们想要的名字。

然而,也不是每个用户都这样认为,所以有的时候,使用一个带有描述属性名字的对象也许是个更好的方式。

除了长度检查,简单的元组类型跟声明了 length 属性和具体的索引属性的 Array 是一样的。

interface StringNumberPair {
  // specialized properties
  length: 2;
  0: string;
  1: number;
 
  // Other 'Array<string | number>' members...
  slice(start?: number, end?: number): Array<string | number>;
}

在元组类型中,你也可以写一个可选属性,但可选元素必须在最后面,而且也会影响类型的 length

type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
  const [x, y, z] = coord;            
   //  const z: number | undefined
 
  console.log(`Provided coordinates had ${coord.length} dimensions`);
  // (property) length: 2 | 3
}

Tuples 也可以使用剩余元素语法,但必须是 array/tuple 类型:

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

有剩余元素的元组并不会设置 length,因为它只知道在不同位置上的已知元素信息:

const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];

console.log(a.length); // (property) length: number

type StringNumberPair = [string, number];
const d: StringNumberPair = ['1', 1];
console.log(d.length); // (property) length: 2

可选元素和剩余元素的存在,使得 TypeScript 可以在参数列表里使用元组,就像这样:

function readButtonInput(...args: [string, number, ...boolean[]]) {
  const [name, version, ...input] = args;
  // ...
}

基本等同于:

function readButtonInput(name: string, version: number, ...input: boolean[]) {
  // ...
}

readonly 元组类型(readonly Tuple Types)

元组类型也是可以设置 readonly 的:

function doSomething(pair: readonly [string, number]) {
  // ...
}

这样 TypeScript 就不会允许写入readonly 元组的任何属性:

function doSomething(pair: readonly [string, number]) {
  pair[0] = "hello!";
  // Cannot assign to '0' because it is a read-only property.
}

在大部分的代码中,元组只是被创建,使用完后也不会被修改,所以尽可能的将元组设置为 readonly 是一个好习惯。

如果我们给一个数组字面量 const 断言,也会被推断为 readonly 元组类型。

let point = [3, 4] as const;
 
function distanceFromOrigin([x, y]: [number, number]) {
  return Math.sqrt(x ** 2 + y ** 2);
}
 
distanceFromOrigin(point);

// Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
// The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.

尽管 distanceFromOrigin 并没有更改传入的元素,但函数希望传入一个可变元组。因为 point 的类型被推断为 readonly [3, 4],它跟 [number number] 并不兼容,所以 TypeScript 给了一个报错。

TypeScript 系列

TypeScript 系列文章由官方文档翻译、重难点解析、实战技巧三个部分组成,涵盖入门、进阶、实战,旨在为你提供一个系统学习 TS 的教程,全系列预计 40 篇左右。点此浏览全系列文章,并建议顺便收藏站点。

微信:「mqyqingfeng」,加我进冴羽唯一的读者群。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

@btea
Copy link

btea commented Nov 18, 2021

在 JavaScript 中,最基本的将数据成组和分发的方式就是通过对象。
数据成组 应该是 数据分组

@mqyqingfeng
Copy link
Owner Author

mqyqingfeng commented Nov 21, 2021

@btea In JavaScript, the fundamental way that we group and pass around data is through objects. 原文用的是 group,感觉有点只能意会,我就直接取了字典里的翻译:

成组和分组感觉都差不多,哈哈

@btea
Copy link

btea commented Nov 21, 2021

@mqyqingfeng 嗯,是的,意思好像差不多,不过成组比较少见,刚开始读起来总感觉不通畅,还以为写错了

@mqyqingfeng
Copy link
Owner Author

mqyqingfeng commented Nov 21, 2021

@btea 难道是因为我以前 PPT 做得比较多的缘故吗?因为 PPT 和 keynote 里将多个元素组成一起,用的就是名为 "成组" 的功能 😂

@btea
Copy link

btea commented Nov 21, 2021

@mqyqingfeng 哈哈,专业术语

@Hurryliwei
Copy link

Hurryliwei commented Dec 7, 2021

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}
 
interface NotOkay {
  [x: number]: Animal;
  // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Dog;
}

大佬,上面这段代码,怎么改都是报错的呀?

请问如果是这种情况的话 这里的索引类型这个案例怎么才能正确呢?

改成 string 他说重复 改成 number 说无法分配,是不是存在接口 extends 就不可以用索引类型啊? 这种情况该如何办?

@Hurryliwei
Copy link

Hurryliwei commented Dec 7, 2021

type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
  const [x, y, z] = coord;
              
  const z: number | undefined
 
  console.log(`Provided coordinates had ${coord.length} dimensions`);
  // (property) length: 2 | 3
}

我的 ts 版本中不需要写 const z: number | undefined

会提示已经声明过了 然后鼠标放上去 会提示这段代码的

可能是版本的原因吧 大家看到测试过的 有没有相同的问题的呀?

image

@mqyqingfeng
Copy link
Owner Author

@Hurryliwei 我们看下这段代码相关的文字:

虽然 TypeScript 可以同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的。

所以修改为不报错的方法是把数字索引的返回类型设置为字符索引返回类型的子类型,比如这样:

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]: Dog;
  // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Animal;
}

@mqyqingfeng
Copy link
Owner Author

mqyqingfeng commented Dec 7, 2021

@Hurryliwei 非常抱歉,setCoordinate 这段我写错了,其实 const z: number | undefined 这句其实是提示的效果, 我拷贝的时候忘记改成注释了……感谢指正,我已经修改了

@Hurryliwei
Copy link

Hurryliwei commented Dec 7, 2021

@Hurryliwei 我们看下这段代码相关的文字:

虽然 TypeScript 可以同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的。

所以修改为不报错的方法是把数字索引的返回类型设置为字符索引返回类型的子类型,比如这样:

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]: Dog;
  // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Animal;
}

也就是这句话:数字索引的返回类型一定要是字符索引返回类型的子类型

所以 dog 是 animal 的子类型 所以需要满足 number 返回类型对应子类型 而 string 返回类型对应父类型?

大佬 我悟了 我悟了

这一个小知识点 早上一直卡着我

我想 WX 加你好友问的 但是怕您不方便

感谢感谢 太感谢了

@Hurryliwei
Copy link

@Hurryliwei 非常抱歉,setCoordinate 这段我写错了,其实 const z: number | undefined 这句其实是提示的效果, 我拷贝的时候忘记改成注释了……感谢指正,我已经修改了

image

我是怕大家 不懂 这不是错误 大佬 您很棒

@mqyqingfeng
Copy link
Owner Author

@Hurryliwei 没事,微信勇敢来加,参与讨论和指正的同学我都直接邀请入群

@Hurryliwei
Copy link

@Hurryliwei 没事,微信勇敢来加,参与讨论和指正的同学我都直接邀请入群

那我加了哦 就是怕问多了 您会烦 也害怕打扰您工作

@w-l-l
Copy link

w-l-l commented Jan 6, 2022

image
单词拼错了:interfaces

@slogeor
Copy link

slogeor commented Jan 28, 2022

@Hurryliwei 我们看下这段代码相关的文字:

虽然 TypeScript 可以同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的。

所以修改为不报错的方法是把数字索引的返回类型设置为字符索引返回类型的子类型,比如这样:

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]: Dog;
  // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Animal;
}

也就是这句话:数字索引的返回类型一定要是字符索引返回类型的子类型

所以 dog 是 animal 的子类型 所以需要满足 number 返回类型对应子类型 而 string 返回类型对应父类型?

大佬 我悟了 我悟了

这一个小知识点 早上一直卡着我

我想 WX 加你好友问的 但是怕您不方便

感谢感谢 太感谢了

读了2遍,我也懂了

@slogeor
Copy link

slogeor commented Jan 28, 2022

@JararvisQ 课代表,催更了

@Sheepeer
Copy link

Sheepeer commented Feb 11, 2022

"可选属性"这里,似乎把typescript写成JavaScript了:
image

@thisisandy
Copy link

thisisandy commented Feb 11, 2022 via email

@liejiayong
Copy link

liejiayong commented Feb 11, 2022 via email

@lisongyu
Copy link

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

const myArray: StringArray = getStringArray();
const secondItem = myArray[1];

是不是getStringArray 这块应该写一个数组

@shjames
Copy link

shjames commented Apr 28, 2022

ReadonlyArray类型不能修改指的是不能使用类似数组push的一些方法吗?比如let x: readonly string[] = [];x=['1','2']; 直接这样些并没有报错

@shjames
Copy link

shjames commented Apr 28, 2022

avatar
请问大佬,为什么上面这种间接赋值会报错?而下面这种直接赋值就不会?
avatar

@liejiayong
Copy link

liejiayong commented Oct 11, 2022 via email

@chenbhui
Copy link

ReadonlyArray类型不能修改指的是不能使用类似数组push的一些方法吗?比如let x: readonly string[] = [];x=['1','2']; 直接这样些并没有报错

ReadonlyArray类型,它与Array相似,只是把所有可变方法(push、pop等)去掉了,因此可以确保数组创建后再也不能被修改,我们也只能通过数组元素索引来访问只读数组元素,但是不能修改只读数组元素。
在进行赋值操作时,允许将常规数组类型赋值给只读数组类型,但是不允许将只读数组类型赋值给常规数组类型。换句话说,不能通过赋值操作来放宽对只读数组的约束。

@lsc9
Copy link

lsc9 commented Apr 11, 2023

@shjames

avatar
请问大佬,为什么上面这种间接赋值会报错?而下面这种直接赋值就不会?
avatar

上面的是数组类型赋值给元组类型,所以报错;
下面的直接是元组类型。

@lujunan1109
Copy link

接口继承和交叉类型里面下面这句话是不是需要把交集改成并集呢

取得是 string 和 number 的交集

@jizai1125
Copy link

@btea

在 JavaScript 中,最基本的将数据成组和分发的方式就是通过对象。
数据成组 应该是 数据分组

我觉得翻译成这样好理解一点:
"在JavaScript中,我们组织和传递数据的基本方式是通过对象。"

@btea
Copy link

btea commented May 6, 2023

@jizai1125 这种描述似乎更加清晰 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests