繁体   English   中英

Typescript 类型转换 object 所以特定的必需键在类型中不再是可选的?

[英]Typescript typecast object so specific required keys are no longer optional in the type?

假设您有一个 object 类型:

Type Person = {
  name?: string;
  color?: string;
  address? string;
}

但是,您想将该类型更改为以下类型,您知道名称和颜色将存在。

Type Person = {
  name: string;
  color: string;
  address? string;
}

因此,有function

const throwIfUndefined = (
  object: {[key: string]: any},
  requiredKeys: string[]
): ReturnTypeHere => {
  for (const key of requiredKeys) {
    if (!object[key]) throw new Error("missing required key");
  }

  return object;
};

输入 function 的参数以及返回类型( ReturnTypeHere )的正确方法是什么? 写得正确,下面将要么 1)抛出错误 2)控制台记录名称。 它永远不会控制台未定义的日志。

const person = {...}

const requiredKeys = ["name", "color"];
const verifiedPerson = throwIfUndefined(person, requiredKeys);
console.log(verifiedPerson.name)

如果你有一个 object 类型T和一个你想要的键K的联合,你可以这样写RequireKeys<T, K>

type RequireKeys<T extends object, K extends keyof T> =
  Required<Pick<T, K>> & Omit<T, K>;

这里我们使用Required<T>Pick<T, K>Omit<T, K>实用程序类型。 这里有可能的边缘情况,例如如果T具有字符串索引签名并且string出现在K中,但对于第一个近似值,它应该可以工作。

也有点难以理解RequiredKeys<Person, "name" | "color"> RequiredKeys<Person, "name" | "color">来自它在 IDE 中的显示方式:

type VerifiedPerson = RequireKeys<Person, "name" | "color">;
// type VerifiedPerson = Required<Pick<Person, "name" | "color">> & 
//   Omit<Person, "name" | "color">

如果您希望编译器更加明确,可以执行以下操作将类型扩展为其属性:

type RequireKeys<T extends object, K extends keyof T> =
  (Required<Pick<T, K>> & Omit<T, K>) extends
  infer O ? { [P in keyof O]: O[P] } : never;

这导致

/* type VerifiedPerson = {
    name: string;
    color: string;
    address?: string | undefined;
} */

这更容易看到。

--

然后您需要将throwIfUndefined()通用 function以便编译器可以跟踪传入的objectrequiredKeys之间的关系:

const throwIfUndefined = <T extends object, K extends keyof T>(
  object: T,
  requiredKeys: readonly K[]
) => {
  for (const key of requiredKeys) {
    if (!object[key]) throw new Error("missing required key");
  }
  return object as unknown as RequireKeys<T, K> // need to assert this
};

并测试:

const person: Person = {
  ...Math.random() < 0.8 ? { name: "Alice" } : {},
  ...Math.random() < 0.8 ? { color: "Color for a person is problematic" } : {}
};
const requiredKeys = ["name", "color"] as const;
const verifiedPerson = throwIfUndefined(person, 
  requiredKeys); // possible runtime error here
// const verifiedPerson: RequireKeys<Person, "name" | "color">

如果您希望编译器记住文字类型"name""color"requiredKeys的成员,那么您需要执行类似const断言(即as const )之类的操作来告诉它。 否则requiredKeys将只是string[]并且您会得到奇怪/错误的结果(我们可以防范这些,但它可能会超出 scope 此处)。

现在,编译器知道namecolor已定义,而address仍然是可选的:

console.log(verifiedPerson.name.toUpperCase() + ": " +
  verifiedPerson.color.toUpperCase()); // no compile error
// [LOG]: "ALICE: COLOR FOR A PERSON IS PROBLEMATIC"

verifiedPerson.address // (property) address?: string | undefined

Playground 代码链接

您可以使用以下类型:

type Require<T, K extends keyof T> = T & { [P in K]-?: T[P]; }

使某些道具需要。 例如

type P = Require<Person, "name" | "color">

所以你现在可以用它来输入你的 function:

const throwIfUndefined = <T, K extends keyof T>(
    object: T,
    requiredKeys: K[]
): Require<T, K> => {
    if(requiredKeys.some(key => typeof object[key] === "undefined"))
        throw new Error("missing required key");    
    return object as Require<T, K>;
};

请考虑下一个示例。

我认为typeguards是这里最合适的解决方案:

type Person = {
    name?: string;
    color?: string;
    address?: string;
}

const hasProperty=<T,P extends string>(obj:T,prop:P):obj is T & Record<P, unknown>=>
    Object.prototype.hasOwnProperty.call(obj, prop);

const throwIfUndefined = <T, K extends string>(
    obj: T,
    requiredKeys: K[]
): obj is T & Record<K, unknown> => requiredKeys.every(key => hasProperty(obj,key));

const person = {
    name: 'John',
    color: 'red',
    address: 'Ternopil'
}

const check = (arg: unknown) => {
    if (throwIfUndefined(arg, ['name'])) {
        const x = arg.name // ok
    }

    if (throwIfUndefined(arg, ['name', 'age'])) {
        const x = arg.name // ok
        const y = arg.age // ok
        const z = arg.other // error

    }
}

我决定从throwIfUndefined返回 boolean ,因为这就是 typeguardss 的工作方式。 在这里,您有完全通用的解决方案。 throwIfUndefined(arg, ['name', 'age'])表示arg变量 100% 包含nameage属性

如果你仍然想抛出错误,你可以使用assert函数。

在这里您可以找到文档。

暂无
暂无

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

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