繁体   English   中英

Typescript:断言未知输入的类型为 Pick<concretetype, subset of keys concretetype> 对于指定的键</concretetype,>

[英]Typescript: Assert unknown input has type Pick<ConcreteType, subset of keys of ConcreteType> for specified keys

当尝试创建一个通用的 function 来测试未知输入是否是已知 object 类型的子集时,我遇到了 Typescript 的问题。我想指定应该存在哪些键并断言输入的类型为 Pick<ConcreteType, subset ConcreteType> 的键数。 我的主张

简化代码:

type Rectangle = {
  width: number,
  height: number
}

const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
  o,
) => {
  if (typeof o !== `object`) {
    throw new Error();
  }
};

function assertRectangle <K extends keyof Rectangle>(o: unknown, ...keys: K[]): asserts o is Pick<Rectangle, K>  {
  assertObject(o);
  // >>>>>>>>>>>>>>>>>>>>>> HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  if (keys.includes(`width` as K) && !o.hasOwnProperty('width')) {
    throw new Error('');
  }
  if (keys.includes(`height` as K) && !o.hasOwnProperty('height')) {
    throw new Error('');
  }
}

const rect = {width: 1, height: 1};
assertRectangle(rect, 'width'); // Pick<Rectangle, "width">
assertRectangle(rect, 'height', 'width'); // Pick<Rectangle, "height" | "width">

这段代码有效,但如果我们删除keys.includes中的as K则无效。

if (keys.includes(`width`) && !o.hasOwnProperty('width')) {
   throw new Error('');
}
// OR:
if (keys.includes(`width` as const) && !o.hasOwnProperty('width')) {
   throw new Error('');
}

“宽度”类型的参数不可分配给“K”类型的参数。 “宽度”可分配给类型“K”的约束,但“K”可以用约束“keyof Rectangle”的不同子类型实例化。ts(2345)

我想知道为什么as const在这里不起作用,我想知道的问题是当我或同事决定更改或重命名 Rectangle 类型的属性时,我希望 typescript 在这个断言不再涵盖时警告我方式。 添加、重命名或减去类型的属性不会被此断言捕获。

在评论中你说:

我这样做是因为每个属性/键都可以有单独的检查。 例如 Rectangle 可以有一个 id 字段,我还想断言该 id 是否属于字符串类型并匹配 uuid 正则表达式

在那种情况下,我会这样处理:

  1. 遍历keys检查它们是否存在

  2. 对于需要额外检查的属性,使用if / else if来识别它们并应用额外检查。 (我很惊讶地发现switch不会抱怨永远无法到达的情况,但if会的话。)

类似于以下内容 - 请注意,在此示例中,我检查了Rectangle上不存在的属性,TypeScript 就此警告我。 这是为了证明您的“我想知道的是,当我或同事决定更改或重命名 Rectangle 类型的属性时”的情况(在这种情况下,假设Rectangle曾经有id但现在没有了)。

type Rectangle = {
    width: number,
    height: number;
};

const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
    o,
) => {
    if (o === null || typeof o !== `object`) {
//      ^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− added
        throw new Error();
    }
};

function assertRectangle<K extends keyof Rectangle>(
    o: unknown,
    ...keys: K[]
): asserts o is Pick<Rectangle, K> {
    assertObject(o);
    for (const key of keys) {
        // Basic check
        if (!o.hasOwnProperty(key)) {
            throw new Error("");
        }
        // Additional per-property checks
        // (I was surprised that `switch` didn't work here to call out property
        // names that aren't on Rectangle like "id" below.)
        if (key === "width" || key === "height") {
            if (typeof o[key] !== "number") {
                throw new Error(`typeof of '${key}' expected to be 'number'`);
            }
        } else if (key === "id") {
//                 ^^^^^^^^^^^^−−−−−−−− causes error because `id` isn't a valid
//                                      Rectangle property (e.g., if you remove
//                                      a property from `Rectangle`, TypeScript
//                                      warns you)
            const id = o[key];
            if (typeof id !== "string" || !id) {
                throw new Error(`'${key}' expected to be non-empty string`);
            }
        }
    }
}

declare let rect1: unknown;
declare let rect2: unknown;
declare let rect3: unknown;
assertRectangle(rect1, "width");
rect1; // <== type is Pick<Rectangle, "width">
assertRectangle(rect2, "height", "width");
rect2; // <== type is Pick<Rectangle, "height" | "width">
assertRectangle(rect3, "height", "width", "not-rectangle-property");
// Error −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^

游乐场链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM