打字稿中的类型分配
Type assignment in Typescript
我不明白为什么在 Typescript 中,我在可变赋值中有这个错误;我认为类型是兼容的,不是吗?
要工作,我必须添加:
async () => {
dataFiles = <Array<DataFileLoadingInstructionType>>await Promise.all(
但是为什么呢?
错误:
类型
'(DataFile | { file_path: string; header_line: number; sheets: never[]; type: string; })[]'
不能分配给类型
'({ file_path: 字符串; } & { file_info?: 字符串 | 未定义; file_name_suffix?: 字符串 | 未定义; command_parameters?: 字符串[] | 未定义; } & { 类型:“未知”;})[]'。
这是我的 index.ts 示例:
import { DataFileLoadingInstructionType } from "./types_async";
import * as path from "path";
type DataFile = {
file_path: string;
type: "unknown";
};
const originalDataFiles: Array<DataFile> = [];
originalDataFiles.push({ file_path: "myFile.txt", type: "unknown" });
let dataFiles: Array<DataFileLoadingInstructionType>;
async function convertPathIfLocal(dataFile: string) {
if (dataFile.indexOf("://") === -1 && !path.isAbsolute(dataFile)) {
dataFile = path.join("my_dir/", dataFile);
}
return dataFile;
}
(async () => {
//here, I have to add <Array<DataFileLoadingInstructionType>> to work
dataFiles = await Promise.all(
originalDataFiles
.filter((f) => f !== undefined)
.map(async (dataFile) => {
if (typeof dataFile === "string") {
return {
file_path: await convertPathIfLocal(dataFile),
header_line: 0,
sheets: [],
type: "csv",
};
} else {
dataFile.file_path = await convertPathIfLocal(dataFile.file_path);
return dataFile;
}
})
);
console.log(
`OUTPUT: ${JSON.stringify(
dataFiles
)} - type of dataFiles: ${typeof dataFiles}`
);
})();
这是我的 types.ts 示例:
import {
Array,
Literal,
Number,
Partial as RTPartial,
Record,
Static,
String,
Union,
} from "runtypes";
const UnknownDataFileLoadingInstructionTypeOptions = Record({
type: Literal("unknown"),
});
export const DataFileLoadingInstructionType = Record({ file_path: String })
.And(
RTPartial({
file_info: String,
file_name_suffix: String,
command_parameters: Array(String),
})
)
.And(Union(UnknownDataFileLoadingInstructionTypeOptions));
export type DataFileLoadingInstructionType = Static<
typeof DataFileLoadingInstructionType
>;
根据我读到的这些代码片段,类型实际上是不可分配的。
一方面,dataFiles
声明为 Array<DataFileLoadingInstructionType>
类型,换句话说:
declare const dataFiles: Array<
| { file_path: string, file_info?: string, file_name_suffix?: string, command_parameters?: string[] }
| { type: 'unknown' }
>
另一方面,originalDataFiles.filter(...).map(...)
的返回值是:
{
file_path: string // await convertPathIfLocal(dataFile)
header_line: number // 0
sheets: never[] // [] inferred as Array<never>
type: string // "csv"
}
(参见 if
分支返回的对象,在 map
内)
或者:
DataFile
(参见 else
分支返回的对象,在 map
内)
所以,我们最终得到:
dataFiles
类型:
Array<
| { file_path: string, file_info?: string, file_name_suffix?: string, command_parameters?: string[]}
| { type: 'unknown' }
>
await Promise.all(originalDataFiles.filter(...).map(...))
类型:
Array<
| { file_path: string, header_line: number, sheets: never[], type: string }
| DataFile
>
事实上,它们都是不可分配的:
- 类型
DataFileLoadingInstructionType
缺少属性 header_line
、sheets
和 type
- 属性
file_path
存在于 DataFile
但不存在于 UnknownDataFileLoadingInstructionTypeOptions
我会说你应该:
- 将 3 个属性添加到
DataFileLoadingInstructionType
,否则在 map
的 if
分支中调整返回的对象以使其与 DataFileLoadingInstructionType
兼容。
- 从
map
的 else
分支返回的 dataFile
中删除 属性 file_path
,或添加 file_path
属性 到 UnknownDataFileLoadingInstructionTypeOptions
类型。
问题的本质在于误解了字符串文字,类型推断和鸭子类型的局限性。那是相当多,但我会尝试一点一点地解释它。
鸭子打字
“如果它走路像鸭子,叫起来像鸭子,那它一定是鸭子。”
Typescript 的优点之一是您无需实例化 class 即可使其遵循接口。
interface Bird {
featherCount: number
}
class Duck implements Bird {
featherCount: number;
constructor(featherInHundreds: number){
this.featherCount = featherInHundreds * 100;
}
}
function AddFeathers(d1: Bird, d2: Bird) {
return d1.featherCount + d2.featherCount;
}
// The objects just need to have the same structure as the
// expected object. There is no need to have
AddFeathers(new Duck(2), {featherCount: 200});
这为语言创造了很大的灵活性。您在 map
函数中使用的灵活性。您要么创建一个全新的对象,在其中调整一些内容,要么调整现有的 dataFile
。在这种情况下,可以通过创建 returns 一个新的 class 的构造函数或方法轻松解决。如果有很多转换,这可能会导致非常大的 classes.
类型推断
然而,在某些情况下,这种灵活性是有代价的。 Typescript 需要能够推断类型,但在这种情况下出错了。当您创建新对象时,type
属性 被视为 string
而不是模板文字 "unknown"
。这暴露了您的代码中的两个问题。
type
属性 只能包含一个值 "unknown"
因为它被类型化为只有一个值的字符串文字,而不是多个文字的并集。该类型至少应具有 "unknown" | "csv"
类型才能使该值起作用。但是我希望这只是这个例子中的一个问题,因为添加 <Array<DataFileLoadingInstructionType>>
似乎可以为你解决问题,而在这个例子中它会破坏这个例子。
- 但即使您调整它或在此处传递唯一允许的值
"unknown"
,它仍然会抱怨。这就是 Typescript 推断类型的方式,因为您只是在这里分配一个值,它假定它是更通用的字符串,而不是更窄的文字 "csv"
.
解决方案
诀窍是帮助 Typescript 键入您正在创建的对象。我的建议是断言 属性 type
的类型,以便 Typescript 知道字符串赋值实际上是字符串文字。
例子
import {
Literal,
Number,
Partial as RTPartial,
Record,
Static,
String,
Union,
} from "runtypes";
import * as path from "path";
// Create separate type, so that we don't need to assert the type inline.
type FileTypes = "unknown" | "csv"
const UnknownDataFileLoadingInstructionTypeOptions = Record({
type: Literal<FileTypes>("unknown"),
});
export const DataFileLoadingInstructionType = Record({ file_path: String })
.And(
RTPartial({
file_info: String,
file_name_suffix: String,
// Threw an error, commented it out for now.
// command_parameters: Array(String),
})
)
.And(Union(UnknownDataFileLoadingInstructionTypeOptions));
export type DataFileLoadingInstructionType = Static<
typeof DataFileLoadingInstructionType
>;
type DataFile = {
file_path: string;
type: FileTypes;
};
const originalDataFiles: Array<DataFile> = [];
originalDataFiles.push({ file_path: "myFile.txt", type: "unknown" });
let dataFiles: Array<DataFileLoadingInstructionType>;
async function convertPathIfLocal(dataFile: string) {
if (dataFile.indexOf("://") === -1 && !path.isAbsolute(dataFile)) {
dataFile = path.join("my_dir/", dataFile);
}
return dataFile;
}
(async () => {
//here, I have to add <Array<DataFileLoadingInstructionType>> to work
dataFiles = await Promise.all(
originalDataFiles
.filter((f) => f !== undefined)
.map(async (dataFile) => {
if (typeof dataFile === "string") {
return {
file_path: await convertPathIfLocal(dataFile),
header_line: 0,
sheets: [],
type: "csv" as FileTypes,
};
} else {
dataFile.file_path = await convertPathIfLocal(dataFile.file_path);
return dataFile;
}
})
);
console.log(
`OUTPUT: ${JSON.stringify(
dataFiles
)} - type of dataFiles: ${typeof dataFiles}`
);
})();
我不明白为什么在 Typescript 中,我在可变赋值中有这个错误;我认为类型是兼容的,不是吗? 要工作,我必须添加:
async () => {
dataFiles = <Array<DataFileLoadingInstructionType>>await Promise.all(
但是为什么呢?
错误:
类型
'(DataFile | { file_path: string; header_line: number; sheets: never[]; type: string; })[]'
不能分配给类型
'({ file_path: 字符串; } & { file_info?: 字符串 | 未定义; file_name_suffix?: 字符串 | 未定义; command_parameters?: 字符串[] | 未定义; } & { 类型:“未知”;})[]'。
这是我的 index.ts 示例:
import { DataFileLoadingInstructionType } from "./types_async";
import * as path from "path";
type DataFile = {
file_path: string;
type: "unknown";
};
const originalDataFiles: Array<DataFile> = [];
originalDataFiles.push({ file_path: "myFile.txt", type: "unknown" });
let dataFiles: Array<DataFileLoadingInstructionType>;
async function convertPathIfLocal(dataFile: string) {
if (dataFile.indexOf("://") === -1 && !path.isAbsolute(dataFile)) {
dataFile = path.join("my_dir/", dataFile);
}
return dataFile;
}
(async () => {
//here, I have to add <Array<DataFileLoadingInstructionType>> to work
dataFiles = await Promise.all(
originalDataFiles
.filter((f) => f !== undefined)
.map(async (dataFile) => {
if (typeof dataFile === "string") {
return {
file_path: await convertPathIfLocal(dataFile),
header_line: 0,
sheets: [],
type: "csv",
};
} else {
dataFile.file_path = await convertPathIfLocal(dataFile.file_path);
return dataFile;
}
})
);
console.log(
`OUTPUT: ${JSON.stringify(
dataFiles
)} - type of dataFiles: ${typeof dataFiles}`
);
})();
这是我的 types.ts 示例:
import {
Array,
Literal,
Number,
Partial as RTPartial,
Record,
Static,
String,
Union,
} from "runtypes";
const UnknownDataFileLoadingInstructionTypeOptions = Record({
type: Literal("unknown"),
});
export const DataFileLoadingInstructionType = Record({ file_path: String })
.And(
RTPartial({
file_info: String,
file_name_suffix: String,
command_parameters: Array(String),
})
)
.And(Union(UnknownDataFileLoadingInstructionTypeOptions));
export type DataFileLoadingInstructionType = Static<
typeof DataFileLoadingInstructionType
>;
根据我读到的这些代码片段,类型实际上是不可分配的。
一方面,dataFiles
声明为 Array<DataFileLoadingInstructionType>
类型,换句话说:
declare const dataFiles: Array<
| { file_path: string, file_info?: string, file_name_suffix?: string, command_parameters?: string[] }
| { type: 'unknown' }
>
另一方面,originalDataFiles.filter(...).map(...)
的返回值是:
{
file_path: string // await convertPathIfLocal(dataFile)
header_line: number // 0
sheets: never[] // [] inferred as Array<never>
type: string // "csv"
}
(参见 if
分支返回的对象,在 map
内)
或者:
DataFile
(参见 else
分支返回的对象,在 map
内)
所以,我们最终得到:
dataFiles
类型:Array< | { file_path: string, file_info?: string, file_name_suffix?: string, command_parameters?: string[]} | { type: 'unknown' } >
await Promise.all(originalDataFiles.filter(...).map(...))
类型:Array< | { file_path: string, header_line: number, sheets: never[], type: string } | DataFile >
事实上,它们都是不可分配的:
- 类型
DataFileLoadingInstructionType
缺少属性 - 属性
file_path
存在于DataFile
但不存在于UnknownDataFileLoadingInstructionTypeOptions
header_line
、sheets
和 type
我会说你应该:
- 将 3 个属性添加到
DataFileLoadingInstructionType
,否则在map
的if
分支中调整返回的对象以使其与DataFileLoadingInstructionType
兼容。 - 从
map
的else
分支返回的dataFile
中删除 属性file_path
,或添加file_path
属性 到UnknownDataFileLoadingInstructionTypeOptions
类型。
问题的本质在于误解了字符串文字,类型推断和鸭子类型的局限性。那是相当多,但我会尝试一点一点地解释它。
鸭子打字
“如果它走路像鸭子,叫起来像鸭子,那它一定是鸭子。”
Typescript 的优点之一是您无需实例化 class 即可使其遵循接口。
interface Bird {
featherCount: number
}
class Duck implements Bird {
featherCount: number;
constructor(featherInHundreds: number){
this.featherCount = featherInHundreds * 100;
}
}
function AddFeathers(d1: Bird, d2: Bird) {
return d1.featherCount + d2.featherCount;
}
// The objects just need to have the same structure as the
// expected object. There is no need to have
AddFeathers(new Duck(2), {featherCount: 200});
这为语言创造了很大的灵活性。您在 map
函数中使用的灵活性。您要么创建一个全新的对象,在其中调整一些内容,要么调整现有的 dataFile
。在这种情况下,可以通过创建 returns 一个新的 class 的构造函数或方法轻松解决。如果有很多转换,这可能会导致非常大的 classes.
类型推断
然而,在某些情况下,这种灵活性是有代价的。 Typescript 需要能够推断类型,但在这种情况下出错了。当您创建新对象时,type
属性 被视为 string
而不是模板文字 "unknown"
。这暴露了您的代码中的两个问题。
type
属性 只能包含一个值"unknown"
因为它被类型化为只有一个值的字符串文字,而不是多个文字的并集。该类型至少应具有"unknown" | "csv"
类型才能使该值起作用。但是我希望这只是这个例子中的一个问题,因为添加<Array<DataFileLoadingInstructionType>>
似乎可以为你解决问题,而在这个例子中它会破坏这个例子。- 但即使您调整它或在此处传递唯一允许的值
"unknown"
,它仍然会抱怨。这就是 Typescript 推断类型的方式,因为您只是在这里分配一个值,它假定它是更通用的字符串,而不是更窄的文字"csv"
.
解决方案
诀窍是帮助 Typescript 键入您正在创建的对象。我的建议是断言 属性 type
的类型,以便 Typescript 知道字符串赋值实际上是字符串文字。
例子
import {
Literal,
Number,
Partial as RTPartial,
Record,
Static,
String,
Union,
} from "runtypes";
import * as path from "path";
// Create separate type, so that we don't need to assert the type inline.
type FileTypes = "unknown" | "csv"
const UnknownDataFileLoadingInstructionTypeOptions = Record({
type: Literal<FileTypes>("unknown"),
});
export const DataFileLoadingInstructionType = Record({ file_path: String })
.And(
RTPartial({
file_info: String,
file_name_suffix: String,
// Threw an error, commented it out for now.
// command_parameters: Array(String),
})
)
.And(Union(UnknownDataFileLoadingInstructionTypeOptions));
export type DataFileLoadingInstructionType = Static<
typeof DataFileLoadingInstructionType
>;
type DataFile = {
file_path: string;
type: FileTypes;
};
const originalDataFiles: Array<DataFile> = [];
originalDataFiles.push({ file_path: "myFile.txt", type: "unknown" });
let dataFiles: Array<DataFileLoadingInstructionType>;
async function convertPathIfLocal(dataFile: string) {
if (dataFile.indexOf("://") === -1 && !path.isAbsolute(dataFile)) {
dataFile = path.join("my_dir/", dataFile);
}
return dataFile;
}
(async () => {
//here, I have to add <Array<DataFileLoadingInstructionType>> to work
dataFiles = await Promise.all(
originalDataFiles
.filter((f) => f !== undefined)
.map(async (dataFile) => {
if (typeof dataFile === "string") {
return {
file_path: await convertPathIfLocal(dataFile),
header_line: 0,
sheets: [],
type: "csv" as FileTypes,
};
} else {
dataFile.file_path = await convertPathIfLocal(dataFile.file_path);
return dataFile;
}
})
);
console.log(
`OUTPUT: ${JSON.stringify(
dataFiles
)} - type of dataFiles: ${typeof dataFiles}`
);
})();