首页 > 解决方案 > 如何防止使用其中一种类型的可选属性时出错

问题描述

Typescript linter 希望查看由 .children返回的所有类型的属性transform。当我已经写支票时如何解决它prop || []

interface NestedItem{
  children?: NestedItem[]
}
function buildTree<Item, TransformedItem extends NestedItem>(
  data: Item[],
  transform: (item: Item) => TransformedItem | Item = item => item
) {
  const result = []
  for (const item of data) {
    const transformedItem = transform({...item})
    const resultItem = Object.assign({}, transformedItem, {
      children: transformedItem.children || [] // TS2339: Property 'children' does not exist on type 'Item | TransformedItem'. Property 'children' does not exist on type 'Item'.
    })
    result.push(resultItem)
  }
  
  return result
}

标签: typescript

解决方案


编译器不知道是否Item会有children属性。一般来说,TypeScript 对象类型是可扩展的/开放的,而不是精确的/封闭的(如microsoft/TypeScript#12936 中);如果对象类型的定义中没有提到某个属性,这并不意味着该类型的值将缺少该键的属性;这意味着编译器不知道那个键是什么(如果有的话)。由于 for 的类型声明Item没有提及children属性的特定类型,因此编译器将其视为可能存在,类型为unknownor any,并且对该属性的任何访问都被视为错误。

因此,工会Item | NestedItem也有同样的问题;因为它可能是一个Item并且我们不能仅仅访问children它。


解决此问题的一种方法是进行约束 Item,以便如果它确实具有children属性,它将是undefined

function buildTree<
    Item extends { children?: undefined }, 
    TransformedItem extends NestedItem
>(
    data: Item[],
    transform: (item: Item) => TransformedItem | Item = item => item
) {
    const result = []
    for (const item of data) {
        const transformedItem = transform({ ...item })
        const resultItem = Object.assign({}, transformedItem, {
            children: transformedItem.children || [] // no error
        })
        result.push(resultItem)
    }    
    return result
}

这解决了实现中的问题,尽管它可能更难调用buildTree(),因为您必须保证data元素必然缺少children属性......正如我所提到的,这不是它的工作方式:

interface Foo {
    a: string;
}
const foos: Foo[] = [{ a: "A" }, { a: "B" }];    
buildTree(foos, x => x) // error!
// -----> ~~~~
//  type 'Foo[]' is not assignable to type '{ children?: undefined; }[]

发生该错误是因为编译器无法保证 aFoo没有children属性。从技术上讲,这是事实:

const hmm = { a: "C", children: "no thanks" };
foos.push(hmm);

因此,此版本为您提供最大的类型安全性,但牺牲了便利性。


如果您不担心data' 的元素可能具有某些children属性的可能性,那么您可以在 的实现中使用类型断言buildTree()来告诉编译器transformedItem' 的children属性要么丢失要么NestedItem

function buildTree<Item, TransformedItem extends NestedItem>(
    data: Item[],
    transform: (item: Item) => TransformedItem | Item = item => item
) {
    const result = []
    for (const item of data) {
        const transformedItem = transform({ ...item })
        const resultItem = Object.assign({}, transformedItem, {
            children: (transformedItem as TransformedItem | 
                { children?: never }).children || []
        })
        result.push(resultItem)
    }

    return result
}

buildTree()因此,如果您调用a ,您将不会收到错误Foo[]

const works = buildTree(foos, x => x); // okay

但是请注意,如果您在数组元素中确实有一个意外children属性,它可能会在运行时导致未捕获的错误,因为您对编译器撒了谎:

for (const w of works) {
    console.log(w.children.join(",")); 
    // "", "", and then  RUNTIME ERROR! w.children.join is not a function
}

Playground 代码链接


推荐阅读