函数第二个参数的对象内容与第一个参数的内容进行绑定问题

新手上路,请多包涵

想要的效果是tag参数输入svg就对应svg的参数, 输入circle就对应circle的参数;
但是if(tag === 'circle')语句中就是不能正常识别, 很纳闷, 也不知道怎么解决. 求教

interface NsTag {
    svg: {
        /** 宽度 */
        width: string;
    };
    circle: {
        /** 圆的中心点到屏幕左边的距离 */
        cx: string;
        /** 圆的中心点到屏幕上边的距离 */
        cy: string;
        /** 半径 */
        r: string;
    };
}

createNsTag('svg', {
    width: '1',
});

function createNsTag<Tag extends keyof NsTag>(tag: Tag, obj: NsTag[Tag]) {

    if(tag === 'circle') {
        /**
         * 此处"cx"抛错错误如下
         * 类型“{ width: string; } | { cx: string; cy: string; r: string; }”上不存在属性“cx”。
         * 类型“{ width: string; }”上不存在属性“cx”。ts(2339)
         */
        obj.cx = '1';
    }

    const eleNs = document.createElementNS('http://www.w3.org/2000/svg', tag);

    for (const attr in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, attr)) {
            /**
             * 此处"element"抛错如下
             * 不能将类型“NsTag[Tag][Extract<keyof NsTag[Tag], string>]”分配给类型“string”。
             * 不能将类型“NsTag[Tag][string]”分配给类型“string”。ts(2322)
             */
            const element: string = obj[attr];
            eleNs.setAttribute(attr, element)
        }
    }
}

image.png

阅读 1.3k
1 个回答

两个办法,一是使用类型断言函数(下面代码中对 circle 的处理),另一种是使用类型转换(对 svg 的处理)

function createNsTag<Tag extends keyof NsTag>(tag: Tag, obj: NsTag[Tag]) {
    function isCircle(tag: Tag, obj: unknown): obj is NsTag["circle"] {
        return tag === "circle";
    }

    if(isCircle(tag, obj)) {
        obj.cx = "1";
    }

    if (tag === "svg") {
        const it = obj as NsTag["svg"];
        it.width = "1";
    }
}

道理:虽然在使用的时候可以知道 obj 的具体类型,但是在定义的时候并不知道啊。虽然 if 里判断了 tag,但是 TSC 不会去对 obj 进行相关类型推断,过程太复杂了,尤其是对普适性需求的时候,所以自己加类型断言。不过自己一个个去写写类型断言真的很累,不如直接做类型转换。

还有一个通用点的 is 函数写法,把 key 传进去

function createNsTag<Tag extends keyof NsTag>(tag: Tag, obj: NsTag[Tag]) {
    function is<T extends keyof NsTag>(t: T, tag: Tag, obj: unknown): obj is NsTag[T] {
        return tag as unknown === t;
    }

    if (is("circle", tag, obj)) {
        obj.cx = "1";
    }

    if (is("svg", tag, obj)) {
        obj.width = "1";
    }
}

如果你要问我为什么 is 就可以推断……我只能告诉你,它不是推断的,是强制的。如果把它的返回类型改成 obj is NsTag["circle"],你会发现 is("svg" ...) 推断出来 obj 仍然是 NsTag["circle"] 类型。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进