Typescript:在数组中存储值时保留精确的元素类型

Typescript:在数组中存储值时保留精确的元素类型,typescript,merge,typescript-typings,typescript-generics,Typescript,Merge,Typescript Typings,Typescript Generics,使用lodash merge考虑以下代码: import { merge } from "lodash"; type Cfg = { a: number b: number c: number }; const cfg: Cfg = merge({ a: 1 }, { b: 2 }, { c: 4 }); 这不会产生错误,因为merge函数保留所有传递对象的精确类型,并且生成的cfg是所

使用lodash merge考虑以下代码:

  import { merge } from "lodash";
    
  type Cfg = {
        a: number
        b: number
        c: number
    };
    
  const cfg: Cfg = merge({ a: 1 }, { b: 2 }, { c: 4 });
这不会产生错误,因为merge函数保留所有传递对象的精确类型,并且生成的
cfg
是所有这些类型的交集(类型
cfg
变成
{a:number}&{b:number}&{c:number}
,因此它与
cfg
兼容)。如果我传递到
merge
某个属性,该属性不在
Cfg
上,或者错过了某些必需的属性,我将得到一个错误

但现在,我需要做同样的事情,但要事先将每个对象存储在某个地方

比如:

import { merge } from "lodash";
    
  type Cfg = {
    a: number
    b: number
    c: number
  };

  const arr: Partial<Cfg>[] = [];

  function addPartial<ArgType extends Cfg>(partial: ArgType) { arr.push(partial); };

  addPartial({ a: 1 });
  addPartial({ b: 2 });
  addPartial({ c: 3 });

  const cfg: Cfg = merge(...arr);
从“lodash”导入{merge};
类型Cfg={
a:号码
b:号码
c:号码
};
常量arr:Partial[]=[];
函数addPartial(partial:ArgType){arr.push(partial);};
addPartial({a:1});
addPartial({b:2});
addPartial({c:3});
const cfg:cfg=merge(…arr);
现在,通过将数组元素的显式类型设置为
Partial
,我丢失了确切的类型,因此我为
cfg
得到的结果类型将是
Partial
,这当然不满足
cfg


是否有任何方法可以逐渐累积这些部分,在不丢失其类型的情况下存储它们,从而可以证明cfg满足cfg类型?

简短的回答不是真的,但特定情况下有可用的解决方法。你所要求的东西没有达到你想要的程度

调用任意函数后,更改某个随机场类型的唯一方法是将该函数设置为a,因此一个选项是使用一个未实际使用的变量,该变量反复声明包含相关字段,然后将其类型用于最终合并:()

声明函数合并(…参数:T[]):T;
//从未实际使用过,只是需要类型累积每次对addPartial的调用
const psuedo_cfg:{}={}
类型Cfg={
a:号码
b:号码
c:号码
};
常量arr:Partial[]=[];
//“| Cfg”对于intellisense很有用,因为只要选择intellisense,在实际给出密钥之前,它不知道哪些密钥是有效的。
函数addPartial(addition:Pick | Cfg,pp=psuedo_Cfg):断言pp是Pick{
arr.push(加法);
}
addPartial({a:5},psuedo_cfg)
addPartial({b:5},psuedo_cfg)
//addPartial({c:5},psuedo_cfg)
const full_config:Cfg=merge(…arr)作为psuedo_Cfg的类型;

我假设
函数addPartial
存储(…)
是同一个函数?一般来说,这是非常困难的,只要做一些像
partcfg1={};设partcfg2=merge(partcfg1,{..});partcfg3=merge(partcfg2,{..})
那么键入就可以了,让append方法像这样编码类型信息真的很难。是的,对不起,我已经更正了。感谢您的回复!是的,事实上这对我来说是一个很难回答的问题(StackOverflow)事实上这是一个高度简化的例子,我在配置管理的JS模块中有这个例子,我现在尝试将它移植到Typescript。其中一个特性是它允许你用许多“块”或“部分”来“扩展”配置(因此,您可以传递配置并从不同的位置进行扩展),对于每个配置,您可以设置优先级,然后在决定“构建”时您的配置所有这些块都是使用合并工具(如lodash merge)按优先级顺序合并的。因此,我不能立即合并它们,因为这会破坏目的。哇,这很巧妙!它可以工作,但我无法理解两件事:1)如果pp参数的默认值为
pseudo\u cfg
,为什么我们每次都要通过考试?但是没有它就不行。2)
如何断言pp是K
生成交叉点类型?我认为,如果我们多次执行
断言
,则应该替换断言的类型,但似乎类型会堆积起来?@YaroslavLarin 1)我认为只是断言默认值没有被视为用例,因此没有实现(除了推断参数类型,typescript从不需要查看默认参数,所以这是一种特殊情况)2)
断言总是取当前类型与断言类型的交集,您可能希望类型为
'a'|'b'| number
的变量断言为
字符串
'a'|'b'
,十字路口比换车更好。有道理,谢谢。在这种情况下,我想我们可以完全放弃违约。我们还可以进一步改进addPartial函数,并通过执行
ts函数addPartial(addition:K,pc:typeof pseudoCfg,):在这种情况下断言pc是K{arr.push(addition)}
无需在
Pick|Cfg
中使用嵌套属性
DeepPartial
是Partial的递归版本(可以在
ts essentials
类型库中找到)@YaroslavLarin我不使用
Partial
DeepPartial
的原因是它允许传递
undefined
null
值,并严格执行null检查。这完全有可能不是您的问题,但一般来说,如果可以使用,使用
Pick
的泛型会更准确。无论如何,如果这对你有效,记得投票或接受:)干杯!已经被提升了,只是还没有出现,因为我的声望还不到15。再次感谢你的帮助!
declare function merge<T>(...args: T[]): T;

// never actually used, just needed for types to accumulate every call to addPartial
const psuedo_cfg: {} = {}

type Cfg = {
    a: number
    b: number
    c: number
  };

const arr: Partial<Cfg>[] = [];

// the `| Cfg` is useful for intellisense, with just Pick<Cfg,K> intelisense doesn't know what keys are valid until they are actually given.
function addPartial<K extends keyof Cfg>(addition: Pick<Cfg,K> | Cfg, pp=psuedo_cfg): asserts pp is Pick<Cfg,K>{
    arr.push(addition);
}

addPartial({a: 5}, psuedo_cfg)
addPartial({b: 5}, psuedo_cfg)
// addPartial({c: 5}, psuedo_cfg)

const full_config: Cfg = merge(...arr) as typeof psuedo_cfg;