Javascript typescript动态安全地向数组添加元素

Javascript typescript动态安全地向数组添加元素,javascript,typescript,dynamic,types,Javascript,Typescript,Dynamic,Types,我正在将Javascript应用程序移植到Typescript。 我有一个对象,它有许多包含数据的数组属性,还有一个javscript函数可以访问数据数组。为了简单起见,我只考虑Add方法< /P> function updateDataStructure(state, structureName, elem) { state.data[structureName].push(elem); return state.data[structureName]; } 移植到typescr

我正在将Javascript应用程序移植到Typescript。 我有一个对象,它有许多包含数据的数组属性,还有一个javscript函数可以访问数据数组。为了简单起见,我只考虑Add方法< /P>
function updateDataStructure(state, structureName, elem) {
   state.data[structureName].push(elem);
   return state.data[structureName];
}
移植到typescript我想介绍类型安全性并维护通用代码

interface AA {
    lep: string,
    snap: number,
    sul: boolean
}

interface BB {
    p1: string;
    val: number;
}

interface StateDataContainer {
    aa: AA[],
    bb: BB[],
}

class State {
    data: StateDataContainer;
}

export type DataContainerProps = keyof StateDataContainer;
type ArrayElement<ArrayType> = ArrayType extends (infer ElementType)[] ? ElementType : never;

function updateDataStructure<K extends DataContainerProps, D extends ArrayElement<StateDataContainer[K]>>
                (state: State, structureName: K, elem: D)
    : StateDataContainer[K] {

    // @ts-ignore
    state.data[structureName].push(elem);
    return state.data[structureName];
}
使用此解决方案,我必须在实现代码中添加使用//@ts ignore注释,以避免编译器错误,如:

 Argument of type 'D' is not assignable to parameter of type 'AA & BB'.
  Type 'ArrayElement<StateDataContainer[K]>' is not assignable to type 'AA & BB'.
    Type '{}' is not assignable to type 'AA & BB'.
      Type '{}' is not assignable to type 'AA'.
        Type 'AA | BB' is not assignable to type 'AA & BB'.
类型为“D”的参数不能分配给类型为“AA&BB”的参数。
类型“ArrayElement”不可分配给类型“AA&BB”。
类型“{}”不可分配给类型“AA&BB”。
类型“{}”不可分配给类型“AA”。
类型“AA | BB”不可分配给类型“AA&BB”。

有没有更好的方法来编写此代码

您的
结构名称
参数的类型为
'aa'|'bb'
,您的
元素
类型为
aa | bb
。TypeScript目前没有像您预期的那样缩小泛型(请参阅)

在您的示例中,请注意输入/输出类型仍然正确(如果您的
structureName
'aa'
,则无法将
传入)。因此,如果您愿意在函数实现中牺牲一些类型安全性,下面是一个让您解锁的黑客:

     state.data[structureName as 'aa'].push(elem as AA);

我认为另一个问题是TypeScript不理解
ArrayElement
类型的语义,也就是说,它不知道它意味着可以附加到
StateDataContainer[K]
的内容

解决这个问题的一种方法是用
D
来定义
state
,即

function updateDataStructure<K extends DataContainerProps, D extends ArrayElement<StateDataContainer[K]>>
    (state: { data: { [key in K]: D[] } }, structureName: K, elem: D)
    : D[] {

    state.data[structureName].push(elem);
    return state.data[structureName];
}
函数更新的数据结构
(状态:{data:{[key in K]:D[]},结构名称:K,元素:D)
:D[]{
state.data[structureName].push(元素);
返回状态。数据[structureName];
}

类型脚本:

是的,你是对的,在方法体中元素的类型是AA | BB。我认为这不是一个安全问题,因为在push时,编译器向我们保证structureName的值和elem的类型将一致。打开structureName值并为每个值添加显式强制转换的自定义push将增加降低安全性的代码行数。我在想是否有可能将
AA | BB转换为AA&BB
老实说,我甚至在考虑将它进一步向相反的方向移动,并将
structureName
elem
设置为
any
。在这一点上,更多的变化最多只能给你带来边际收益。我建议在这段代码上面抛出一个
//TODO
,然后等待编译器在将来的版本中正确处理它。是的,好主意,如果我不想修改签名,我也可以将数组
(state.data[structureName]转换为d[])。push(elem)
state.data[structureName]=(state.data[structureName]作为D[])。filter(v=>v)作为StateDataContainer[K]
function updateDataStructure<K extends DataContainerProps, D extends ArrayElement<StateDataContainer[K]>>
    (state: { data: { [key in K]: D[] } }, structureName: K, elem: D)
    : D[] {

    state.data[structureName].push(elem);
    return state.data[structureName];
}