【问题标题】:Typescript: How to use discriminated unions with structure rather than with discriminant property?打字稿:如何使用具有结构而不是判别属性的可区分联合?
【发布时间】:2018-08-22 11:46:12
【问题描述】:

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

我需要exhaustiveness checking,所以我打开了strictNullChecks。我希望如果它成功创建可区分联合,编译器不会将| undefined 添加到函数retrieveFileContent 中返回的fileContent。但是 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/**/*"
  ]
}

【问题讨论】:

  • 我用typescript@3.0.1

标签: typescript visual-studio-code


【解决方案1】:

据我所知,这种详尽检查仅适用于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
}

希望对您有所帮助。祝你好运!

【讨论】:

  • 我认为函数ifVersion 会起作用,因为它与switchVersion 具有相同的逻辑。但是现在我知道它实际上会像您指出的那样被破坏。而且您的解决方案效果很好!非常感谢!
猜你喜欢
  • 2020-05-01
  • 2020-01-25
  • 2023-03-30
  • 1970-01-01
  • 2019-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多