首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >类型' number‘不能赋值给类型'T[keyof T]',但'T[keyof T]’是数字

类型' number‘不能赋值给类型'T[keyof T]',但'T[keyof T]’是数字
EN

Stack Overflow用户
提问于 2020-06-05 08:15:56
回答 1查看 364关注 0票数 0

解决这个问题的正确方法是:

代码语言:javascript
复制
function getSumMapAndCount<T extends Partial<Record<string, number>>>({
  data,
  keys
}: {
  data: readonly T[],
  keys: readonly (keyof T)[]
}): [T, number] {
  let dataCount = 0

  const dataSumMap = data.reduce<T>((dataMap, datum) => {
    const keysToAdd = _.intersection(Object.keys(datum), keys)
    // Only count if at least one key exists
    if (keysToAdd.length === 0) {
      return dataMap
    }

    dataCount++
    keysToAdd.forEach(key => {
      const sum = dataMap[key] as number || 0
      const value = datum[key] as number || 0
      dataMap[key] = sum + value // Type 'number' is not assignable to type 'T[keyof T]'.ts(2322)
    })

    return dataMap
  }, {} as T)

  return [dataSumMap, dataCount]
}

问题是Tkeyof T可以是number | undefined,那么为什么它会出错呢?我知道你可以用

代码语言:javascript
复制
dataMap[key] = (sum + value) as T[keyof T]

但我感觉这里有更深层次的东西在起作用。

EN

回答 1

Stack Overflow用户

发布于 2020-06-05 09:39:38

一个问题是T extends Partial<Record<string, number>>接受属性比number更窄的类型,比如numeric literal types

代码语言:javascript
复制
type Month = { days: 28 | 29 | 30 | 31; }
const months: Month[] = [{ days: 31 }, { days: 28 }, { days: 31 }, { days: 30 }];

const days = getSumMapAndCount({ data: months, keys: ["days"] })[0].days;
// const days: 28 | 29 | 30 | 31
console.log(days); // 120 

在这里,Monthdays属性必须是介于28和31之间(包括28和31)的整数。如果我们将一个Month对象数组传递给getSumMapAndCount(),并要求它对days求和,我们将得到一个元组,它的第一个元素声称是一个Month。但它不是一个:它有120天。糟了。

我想情况会变得更糟。如果传入的数据数组中有未包含在keys数组中的键,则getSumMapAndCount()将返回一个元组,其第一个元素声称与数据的类型相同。但它将缺少关键字:

代码语言:javascript
复制
type Point2D = { x: number, y: number };
const points: Point2D[] = [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }];

const point2D = getSumMapAndCount({ data: points, keys: ["x"] })[0];
// const point2D: Point2D;
point2D.y.toFixed(); // no compiler error, runtime 

还有哦。

我认为我们需要后退一步,确切地考虑输入和输出的类型是什么,并正确地为函数编写类型,然后让实现接受它。下面是我认为你的输入:

代码语言:javascript
复制
function getSumMapAndCount<K extends PropertyKey,
    T extends { [P in K]?: number }>({ data, keys }: {
        data: readonly T[],
        keys: readonly K[]
    }): [{ [P in K]?: number }, number] {

对于与keys数组元素对应的联合K中的每个键,我们只约束T不具有该键或在该键处具有number属性。我们不关心其他的钥匙。重要的是,输出类型只是声称是从K中的键(某个子集)到number的映射。因此,如果TMonthK"days",则输出的第一个元素将是{days?: number},而不是Month。那很好。让我们看看在实现中必须发生什么:

代码语言:javascript
复制
    let dataCount = 0

    const dataSumMap = data.reduce((dataMap, datum) => {
        const keysToAdd = keys.filter(k => k in datum); // changed this
        // Only count if at least one key exists
        if (keysToAdd.length === 0) {
            return dataMap
        }

        dataCount++
        keysToAdd.forEach(key => {
            const sum = dataMap[key] as number || 0
            const value = datum[key] as number || 0
            dataMap[key] = sum + value;
        })

        return dataMap
    }, {} as { [P in K]?: number })

    return [dataSumMap, dataCount]
}

唯一真正的区别是,我断言初始映射是一个Partial<Record<K, number>> (或等效的{[P in K]?: number}),而不是T。然后,其他一切都会正常工作。(请注意,我没有lodash,所以我将intersection的使用改为数组过滤器;在那里做您想做的事情)。让我们看看它是否起作用:

代码语言:javascript
复制
const days = getSumMapAndCount({ data: months, keys: ["days"] })[0].days;
// const days: number | undefined
console.log(days); // 120 

const justX = getSumMapAndCount({ data: points, keys: ["x"] })[0];
// const justX: {x?: number | undefined} 
justX.y.toFixed(); // compiler error!
//    ~ <-- there's no y on {x?: number | undefined}

现在看起来好多了。

好的,希望这会有帮助;祝你好运!

Playground link to code

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62206138

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档