打字稿:如何使用具有结构而不是具有判别属性的有区别的联合?

时间:2018-08-22 11:46:12

标签: typescript visual-studio-code

我想编写一个从源文件检索文件内容的函数。源文件只有两种结构。我想将discriminated unionsuser-defined type guard一起使用来处理内容。

我需要exhaustiveness checking,所以我打开了strictNullChecks。我希望,如果它成功创建了有区别的联合,则编译器将不会将| undefined添加到函数fileContent中返回的retrieveFileContent中。但是Typescript编译器会抛出错误:

error TS2322: Type '{ type: FileContentType; payload: string; } | { type: FileContentType; payload: { filename: string; path: string; }; } | undefined' is not assignable to type 'FileContent'.
Type 'undefined' is not assignable to type 'FileContent'

如何修改我的代码以使用用户定义的类型保护权进行区分联合?还是我的用例还有其他更好的解决方案?

代码:

// Target File Content Format

enum FileContentType {
  disk,
  memory,
}

interface FileContentInMemory {
  type: FileContentType.memory
  payload: string
}

interface FileContentInDisk {
  type: FileContentType.disk
  payload: {
    filename: string
    path: string
  }
}

type FileContent = FileContentInMemory | FileContentInDisk

// Source File Containing Content

interface FileBasics {
  fieldname: string
  originalname: string
  encoding: string
  mimetype: string
  size: number
}

interface FileInMemory extends FileBasics {
  fileString: string
}

interface FileInDisk extends FileBasics {
  filePath: string
}

type SourceFile = FileInMemory | FileInDisk

function isInMemory(file: SourceFile): file is FileInMemory {
  return (<FileInMemory>file).fileString !== undefined
}

function isInDisk(file: SourceFile): file is FileInDisk {
  return (<FileInDisk>file).filePath !== undefined
}

// Retrieve Content From File

function retrieveFileContent(file: SourceFile): FileContent {
  let fileContent
  if (isInMemory(file)) {
    fileContent = {
      type: FileContentType.memory,
      payload: file.fileString
    }
  } else if (isInDisk(file)) {
    fileContent = {
      type: FileContentType.disk,
      payload: {
        filename: file.originalname,
        path: file.filePath,
      }
    }
  }
  return fileContent
}

我的tsconfig.json

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "esModuleInterop": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "outDir": "dist",
    "sourceMap": true,
    "strictNullChecks": true,
    "rootDir": "src",
    "jsx": "react",
    "types": [
      "node",
      "jest"
    ],
    "typeRoots": [
      "node_modules/@types"
    ],
    "target": "es2016",
    "lib": [
      "dom",
      "es2016"
    ]
  },
  "include": [
    "src/**/*"
  ]
}

1 个答案:

答案 0 :(得分:0)

据我所知,这种穷举性检查仅适用于a switch statement that is the last statement in a function

type DiscriminatedUnion = {k: "Zero", a: string} | {k: "One", b: string};

// works
function switchVersion(d: DiscriminatedUnion): string {
  switch (d.k) {
    case "Zero": return d.a;
    case "One": return d.b;
  }
}

// broken
function ifVersion(d: DiscriminatedUnion): string {
// Error! might not return a string -----> ~~~~~~  
  if (d.k === "Zero") return d.a;
  if (d.k === "One") return d.b;  
}

您正在执行的检查实际上并不适合switch语句。幸运的是,您可以使用the docs中列出的第二种穷举性检查方法:使用一个辅助函数,如果在完成所有检查后未将file缩小到never,则会引发编译器错误:

function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function retrieveFileContent(file: SourceFile): FileContent {
  let fileContent: FileContent; // use annotation to avoid any
  if (isInMemory(file)) {
    fileContent = {
      type: FileContentType.memory,
      payload: file.fileString
    }
  } else if (isInDisk(file)) {
    fileContent = {
      type: FileContentType.disk,
      payload: {
        filename: file.originalname,
        path: file.filePath,
      }
    }
  } else return assertNever(file); // no error, file is never      
  return fileContent; // no error, fileContent is FileContent
}

希望有帮助。祝你好运!