简体   繁体   English

TypeScript 说不存在交集属性

[英]TypeScript Says Intersection Property Does Not Exist

I have a variable that starts out as one type, IPerson[] , but after being mapped a couple of times, should have an _id property added to it, so like Array<IPerson & IWithId> .我有一个以一种类型开始的变量IPerson[] ,但是在映射了几次之后,应该添加一个_id属性,就像Array<IPerson & IWithId>一样。 However, in the fourth-to-last-line, tyring to print the _id property gives me a TS error, even though the property does exist and the logging works like I'd expect, printing the three properties fname , lname , and _id .但是,在倒数第四行中,打印_id属性会给我一个 TS 错误,即使该属性确实存在并且日志记录工作如我所料,打印三个属性fnamelname_id .

I thought that maybe I need to re-cast it somehow, like我想也许我需要以某种方式重新投射它,比如

mapped = collection.map(mapperB) as Array<IPerson & IWithId>

That didn't work, thankfully as it seems super verbose to have to do that for a variable that imo should already be getting its type based on the return type of the mapperB function.这没有奏效,幸运的是,对于 imo 应该已经根据mapperB function 的返回类型获取其类型的变量来说,这样做似乎非常冗长。

let _id = 0;
interface IPerson { 
    fname: string;
    lname: string;
}

interface IWithId { 
    _id: number;
}

function getNumber() { 
    return _id++
}

async function getData(json: string): Promise<IPerson[]> { 
    return JSON.parse(json)
}

function mapperA(entry: IPerson): IPerson { 
    return {
        ...entry,
        lname: entry.lname.toUpperCase()
    }
}
function mapperB(entry: IPerson): IPerson & IWithId { 
    const _id = getNumber();
    return {
        ...entry,
        _id
    } 
}
async function main() {
    const json = `[{"fname":"john","lname":"doe"},{"fname":"jane","lname":"doe"}]`    
    const collection = await getData(json)
    let mapped = collection.map(mapperA)
    mapped = collection.map(mapperB)
    console.log(mapped[0]._id); // Property '_id' does not exist on type 'IPerson'.
    return mapped;
}

main().then(console.log)

I can get this to work if I use another variable to hold the value of the second map function, ie const mapped2 = collection.map(mapperB) but I'm curious why I can't use my original variable?如果我使用另一个变量来保存第二个 map function 的值,我可以让它工作,即const mapped2 = collection.map(mapperB)但我很好奇为什么我不能使用我的原始变量?

Why doesn't typescript infer the value of mapped from the explicitly stated return value of mapperB ?为什么 typescript 不从mapperB的明确声明的返回值推断mapped的值? Can I make it do this for me?我可以让它为我做这个吗?

TypeScript Playground TypeScript 游乐场

TypeScript infers the type of mapped from it's first assignment (initialization), so it's IPerson[] : TypeScript 从它的第一个赋值(初始化)推断mapped的类型,所以它是IPerson[]

In TypeScript, there are several places where type inference is used to provide
type information when there is no explicit type annotation. For example, in this
code

> let x = 3;

The type of the x variable is inferred to be number. This kind of inference takes place
when initializing variables and members, setting parameter default values, and 
determining function return types.

Taken from "Type inference" chapter from the TypeScript handbook (I linked it's upcoming 2.0 beta version), I recommend reading this.摘自TypeScript 手册中的“类型推断”一章(我链接了它即将发布的 2.0 测试版),我建议阅读此内容。

The second assignment then doesn't extend the definition, but is no error either because objects can have additional properties.然后第二个赋值不会扩展定义,但也没有错误,因为对象可以具有其他属性。 When you're accessing _id , you get an error because TypeScript cannot be sure from the initially inferred type that the array entries also contain _id properties.当您访问_id时,您会收到一个错误,因为 TypeScript 无法从最初推断的类型确定数组条目还包含_id属性。

Note: casting with mapped = collection.map(mapperB) as Array<IPerson & IWithId> gives TypeScript no additional information, so the outcome is the same.注意:使用mapped = collection.map(mapperB) as Array<IPerson & IWithId>不会为 TypeScript 提供其他信息,因此结果是相同的。


To ease reasoning about types, I personally recommend assigning transformed values to new variables (as you proposed with const mapped2 = collection.map(mapperB) . And choose expressive variable names (with tradeoff of becoming lengthy, but this shouldn't happen that often if you keep your function complexity small enough):为了简化类型的推理,我个人建议将转换后的值分配给新变量(正如您使用const mapped2 = collection.map(mapperB)提出的那样。并选择富有表现力的变量名称(权衡变得冗长,但这不应该经常发生)如果你保持你的 function 复杂度足够小):

const filteredList = list.filter(...);
const filteredListWithIds = filteredList.map(...)

Not directly related, but an error: Array.prototype.map() returns a new array .不是直接相关,而是一个错误: Array.prototype.map() 返回一个数组 The value of mapped from let mapped = collection.map(mapperA) is lost immediately as it s being overwritten at the next line during mapped = collection.map(mapperB)`. let mapped = collection.map(mapperA)中的mapped值立即丢失,因为它在 mapped = collection.map(mapperB)` s being overwritten at the next line during Maybe a mistake when creating the playground example based on your real code?根据您的真实代码创建游乐场示例时可能会出错?

Yes, you cannot change the type of the variable in typescript once it is assigned.是的,一旦分配了 typescript 中的变量,您就无法更改它的类型。

As mentioned in the above example you can use different variables.如上例所述,您可以使用不同的变量。 But as per your concern, you want to use simply one variable you can call both mappers by chaining them one after the other.但是根据您的关注,您只想使用一个变量,您可以通过将它们一个接一个地链接来调用两个映射器。

Typescript supports the chaining of function calls in very good manner. Typescript 以非常好的方式支持 function 调用的链接。 So you can replace your last two lines of code by the single line as below:因此,您可以用单行替换最后两行代码,如下所示:

let mapped = collection.map(mapperA).map(mapperB)

I hope you find this helpful.我希望你觉得这有帮助。 And you can resolve your error.你可以解决你的错误。

Here the problem is in following lines:这里的问题在于以下几行:

let mapped = collection.map(mapperA) // here you declare mapped with the type IPerson[]
mapped = collection.map(mapperB) // here mapped already has a type and can't be changed
console.log(mapped[0]._id); // here you try to access a property IPerson doesn't have

You can try to solve this either chaining the mappers as per other answers or coercing the two mappers in only one:您可以尝试按照其他答案链接映射器或仅将两个映射器强制为一个来解决此问题:

function mapper(entry: IPerson): IPerson & IWithId {
    const _id = getNumber();

    return {
        ...entry,
        _id,
        lname: entry.lname.toUpperCase()
    }
}

// later in your main function
let mapped = collection.map(mapper); // here mapped is declared as (IPerson & IWithId)[]
console.log(mapped[0]._id); // now you can access any IWithId property

Hope this helps.希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM