【问题标题】:typescript dynamic and safe adding element to array打字稿动态和安全地将元素添加到数组
【发布时间】:2019-03-09 19:06:14
【问题描述】:

我正在将 Javascript 应用程序移植到 Typescript。 我有一个具有许多包含数据的数组属性的对象,并且一个 javscript 函数可以访问数据数组。为简单起见,我只考虑 add 方法

function updateDataStructure(state, structureName, elem) {
   state.data[structureName].push(elem);
   return state.data[structureName];
}

移植到打字稿我想介绍类型安全并维护通用代码。

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];
}

使用这段代码,我有一个类型化的安全接口,编译器检查客户端代码以将正确的对象添加到正确的结构中。

const state = new State();

let avv: BB;
let line: AA;

let v: BB[] = updateDataStructure(state, 'bb', avv!);
let l: AA[] = updateDataStructure(state, 'aa', line!);

使用此解决方案,我必须在实现代码中添加使用 //@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'.

有没有更好的方法来编写这段代码?

【问题讨论】:

    标签: javascript typescript dynamic types


    【解决方案1】:

    我认为另一个问题是 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];
    }
    

    TypeScript 游乐场:link

    【讨论】:

    • 是的好主意,如果我不想修改签名,我也可以键入转换数组 (state.data[structureName] as D[]).push(elem);state.data[structureName] = (state.data[structureName] as D[]).filter( v =&gt; v) as StateDataContainer[K];
    【解决方案2】:

    您的structureName 参数的类型为'aa' | 'bb',而您的elem 类型为AA | BB。 TypeScript 目前并没有像您期望的那样缩小泛型的范围(请参阅#23578#24085)。

    在您的示例中,请注意输入/输出类型仍然正确(如果您的 structureName'aa',则您不能传递 line)。所以,如果你愿意在你的函数实现中牺牲一些类型安全,这里有一个技巧可以让你畅通无阻:

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

    【讨论】:

    • 是的,你是对的,在方法体中,elem 的类型是 AA | BB。我认为这不是安全问题,因为在 push 时,编译器已向我们保证 structureName 的值和 elem 的类型将符合。
    • 打开 structureName 值并使用每个值的显式转换添加自定义推送将增加代码行数,从而降低安全性。我在想是否有可能投AA | BB as AA &amp; BB
    • 老实说,我在考虑甚至将它朝相反的方向进一步发展,并将structureNameelem 都设置为any。在这一点上,更多的变化最多只能给你带来边际收益。我建议在此代码块上方添加// TODO,然后等到编译器在将来的版本中正确处理此问题。
    猜你喜欢
    • 2016-02-09
    • 2017-07-30
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    • 1970-01-01
    • 2017-12-06
    相关资源
    最近更新 更多