【问题标题】:Safe typing of Object.assignObject.assign 的安全类型
【发布时间】:2022-03-11 08:00:47
【问题描述】:

我们正在寻找一种使用 Object.assign 的类型安全方式。但是,我们似乎无法让它工作。

为了显示我们的问题,我将使用 Generics 文档中的 copyFields 方法

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

function makesrc(): Source { return {b: 1, c: "a"}}

interface Source {
    a?: "a"|"b",
    b: number,
    c: "a" | "b"
}

我希望引擎阻止我创建未声明的属性

/*1*/copyFields(makesrc(), {d: "d"}); //gives an error
/*2*/copyFields(makesrc(), {a: "d"}); //gives an error
/*3*/copyFields(makesrc(), {c: "d"}); //should give an error, but doesn't because "a"|"b" is a valid subtype of string.

//I don't want to specify all the source properties 
/*4*/copyFields(makesrc(), {b: 2}); //will not give me an error
/*5*/copyFields(makesrc(), {a: "b"}); //should not give an error, but does because string? is not a valid subtype of string 

我们已尝试通过显式向 copyfields 调用提供类型来解决此问题 但我们找不到可以使所有示例都正常运行的调用。

例如: 要完成 5 项工作,您可以像这样调用 copyFields:

/*5'*/copyFields<Source,{a?:"a"|"b"}>(makesrc(), {a: "b"}); 

但对 Source 类型的后续更改(例如删除“b”选项)现在将不再导致类型错误

有没有人知道如何实现这个功能?

【问题讨论】:

标签: typescript


【解决方案1】:

Typescript 2.1.4 来救援!

Playground link

interface Data {
    a?: "a"|"b",
    b: number,
    c: "a" | "b"
}

function copyFields<T>(target: T, source: Readonly<Partial<T>>): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

function makesrc(): Data { return {b: 1, c: "a"}}

/*1*/copyFields(makesrc(), {d: "d"}); //gives an error
/*2*/copyFields(makesrc(), {a: "d"}); //gives an error
/*3*/copyFields(makesrc(), {c: "d"}); //gives an error

//I don't want to specify all the source properties 
/*4*/copyFields(makesrc(), {b: 2}); //will not give me an error
/*5*/copyFields(makesrc(), {a: "b"}); //will not give me an error

【讨论】:

    【解决方案2】:

    我能想到的最佳解决方法是定义与Source 完全相同的第二个接口(我称之为SourceParts),除了所有成员都是可选的。

    function copyFields<T extends U, U>(target: T, source: U): T {
        for (let id in source) {
            target[id] = source[id];
        }
        return target;
    }
    
    function makesrc(): Source { return {b: 1, c: "a"}}
    
    interface Source {
        a?: "a"|"b",
        b: number,
        c: "a" | "b"
    }
    
    interface SourceParts {
        a?: "a"|"b",
        b?: number,
        c?: "a" | "b"
    }
    
    /*1*/copyFields<Source, SourceParts>(makesrc(), {d: "d"}); //gives an error
    /*2*/copyFields<Source, SourceParts>(makesrc(), {a: "d"}); //gives an error
    /*3*/copyFields<Source, SourceParts>(makesrc(), {c: "d"}); //gives an error
    
    //I don't want to specify all the source properties 
    /*4*/copyFields<Source, SourceParts>(makesrc(), {b: 2}); //will not give me an error
    /*5*/copyFields<Source, SourceParts>(makesrc(), {a: "b"}); //will not give me an error 
    

    这里是Typescript Playground

    【讨论】:

    • 我们确实想到了这个解决方案,但是对于我们的用例,它需要大量的双重模板。
    • 是的,这至少将两个类型定义放在一起,因此您很有可能同时更新它们。
    【解决方案3】:

    我之前做过这个解决方案:

       /**
         * assign with known properties from target.
         *
         * @param target
         * @param source
         */
        public static safeAssignment(target: any,  source: any) {
            if (isNullOrUndefined(target) || isNullOrUndefined(source)) {
                return;
            }
    
            for (const att of Object.keys(target)) {
                target[att] = source.hasOwnProperty(att) ? source[att] : target[att];
            }
        }
    

    我希望有人可以有用。 问候

    【讨论】:

      【解决方案4】:

      您可以使用Object.assign&lt;TargetType, SourceType&gt;(target, source) - 我认为它提供了类型安全性。

      【讨论】:

        【解决方案5】:

        我有这个功能:

           /**
             * Take every field of fields and put them override them in the complete object
             * NOTE: this API is a bit reverse of extend because of the way generic constraints work in TypeScript
             */
            const updateFields = <T>(fields: T) => <U extends T>(complete: U): U => {
                let result = <U>{};
                for (let id in complete) {
                    result[id] = complete[id];
                }
                for (let id in fields) {
                    result[id] = fields[id];
                }
                return result;
            }
        

        用法:

        updateFields({a:456})({a:123,b:123}) // okay
        updateFields({a:456})({b:123}) // Error
        

        ?。

        更多

        我之前在不同的上下文中提到过这个函数:https://stackoverflow.com/a/32490644/390330

        PS:一旦 JavaScript 进入第 3 阶段,情况就会好转:https://github.com/Microsoft/TypeScript/issues/2103

        【讨论】:

        • 不错的解决方案,但是,我们的愿望是(如评论中所述):“我不想指定所有源属性”
        • @revanderark 不必updateFields({a:456})({a:123,b:123}) 只指定a(来源同时具有ab)?
        • 对不起。这个函数真的和我们的一样,但是有柯里化。我在操场上尝试过(链接太长,无法粘贴到这里),但在相同的测试中失败了(3 和 5)
        猜你喜欢
        • 2011-10-26
        • 2012-12-20
        • 1970-01-01
        • 2018-10-06
        • 1970-01-01
        • 2012-10-31
        • 2020-04-29
        • 2011-09-13
        • 1970-01-01
        相关资源
        最近更新 更多