JS has object wrappers to let me check primitive's "property" exists or not, how do I let TS work like that?

huangapple go评论70阅读模式
英文:

JS has object wrappers to let me check primitive's "property" exists or not, how do I let TS work like that?

问题

// 如何让它在ts中工作?
英文:

The return value of XMLValidator.validate is either true or ValidationError (which is not exactly correct, check my update), which has err property.

validate(  xmlData: string,  options?: validationOptionsOptional): true | ValidationError

Because javascript uses object wrappers for boolean primitive I can just check whether the err property exists or not to see if the validation succeeds.

const result = XMLValidator.validate(message)
if (result.err) { // How do I make it work in ts?
   console.log(`invalid XML)
} else {
  console.log(`XML)
}

But typescript won't let me do that, it will complain Property 'err' does not exist on type 'true'

I don't like the idea of checking the return type first because I feel it is wordy and there is no return value type definition actually (check my update). How do I write the ts code here as concise as my js code?

--- update ---

I further check validator.js code

const isValid = validateAttributeString(attrStr, options);
if (isValid !== true) {
   return getErrorObject(...);
}
...

function getErrorObject(code, message, lineNumber) {
  return {
    err: {
      code: code,
      msg: message,
      line: lineNumber.line || lineNumber,
      col: lineNumber.col,
    },
  };
}

So it seems that I can only do if (typeof result == "boolean") here but I hope there is any "general" solution to my question.

答案1

得分: 2

你说得对,TypeScript不会允许你访问一个变量类型上不存在的属性。如果该类型是一个联合类型,那么在 TypeScript 允许你访问它之前,该属性必须存在于 所有 联合类型的成员上。

但是可以通过各种方法来缩小变量的类型。例如,你可以首先检查值是否为 true,如果不是,TypeScript 将会缩小该联合类型只包含 ValidationError

通常情况下,另一个选项是使用 in 运算符,但在这种情况下,联合类型不仅包含对象类型,所以 TypeScript 不会允许它。

你还可以定义一个自定义类型保护(或者如果库中已经存在一个类型保护的话,可以使用它),但对于这种简单的情况,这可能对你的需求来说有点过于繁琐。

正如我链接的 TypeScript 文档详细说明的那样,还有其他缩小类型的方法。检查 typeof 运算符的结果,就像你提到的那样,是其中的一种方法。

这个示例只声明了一个类型保护的存在,而没有实现一个,但下面是如何使用这些方法来缩小 result 变量类型的示例:

declare type ValidationError = { err: unknown };
declare function isValidationError(val: unknown): val is ValidationError;

declare const XMLValidator: {
  validate(message: unknown): true | ValidationError
};

const message = 'test message';

const result = XMLValidator.validate(message)

// 你可以首先检查结果是否为 `true`
if (result === true) {
  // 处理成功情况
} else {
  // 然后 `else` 分支知道它一定是 `ValidationError`
  console.error(result.err);
}

// 通常是允许的,但在这种情况下不行。错误:
// 类型 'true | ValidationError' 不能分配给类型 'object'
if ('err' in result) {
  console.error(result.err)
}

if (isValidationError(result)) {
  console.error(result.err);
}

TypeScript Playground

虽然通常最好避免使用,但你还可以使用类型断言,通过 TypeScript 的 as 关键字。

之所以要避免使用它们,是因为你在告诉 TypeScript 将一种类型视为另一种类型。因此,这样做可能会牺牲类型安全性。

console.error((result as ValidationError).err);

然而,在你比 TypeScript 编译器更了解发生了什么的情况下,这是你可以用来提供更多信息的工具。

个人而言,我发现始终留下一条注释,解释为什么要使用类型断言,提到其安全性基于的任何假设,是一种有帮助的方法。

另外,在这种情况下,使用类型断言来检查属性不会缩小 result 变量的类型,所以这种方法可能不完全符合你的需求。

英文:

You're right that TypeScript won't let you access a property that it doesn't know can exist on a variable's type. If that type is a union, then the property must exist on all members of that union before TypeScript will let you access it.

But it is possible to narrow the type of a variable through various methods. For example, you could first check if the value is true, and if it's not then TypeScript will narrow the union down to just ValidationError.

Normally, another option would be to use the in operator, but in this case the union doesn't only contain object types so TypeScript won't allow it.

You could also define a custom type guard (or use one provided by the library, if one exists), but for a simple case like this that seems like it's probably a bit too much work for what you want.

As the TypeScript documentation I linked to details, there are other ways of narrowing types too. Checking the result of the typeof operator, like you've mentioned, is another one of those methods.

This example just declares that a typeguard exists, rather than implementing one, but here's how you could use each of those approaches to narrow the type of your result variable:

declare type ValidationError = { err: unknown };
declare function isValidationError(val: unknown): val is ValidationError;

declare const XMLValidator: {
  validate(message: unknown): true | ValidationError
};

const message = 'test message';

const result = XMLValidator.validate(message)

// You can check first if the result is `true`
if (result === true) {
  // Handle success
} else {
  // Then the `else` branch knows it much be a `ValidationError`
  console.error(result.err);
}

// Normally allowed, but not in this case. Error:
// Type 'true | ValidationError' is not assignable to type 'object'
if ('err' in result) {
  console.error(result.err)
}

if (isValidationError(result)) {
  console.error(result.err);
}

TypeScript Playground


Though they're usually best avoided, another option available to you is to use a type assertion via TypeScript's as keyword.

The reason why these are best avoided is because you're telling TypeScript to treat a type as though it were something else. So you can compromise type safety by doing this.

console.error((result as ValidationError).err);

However, in cases where you understand what's going on better than the TypeScript compiler does, this is the tool you can use to provide more information to it.

Personally, I find it's a helpful approach to always leave a comment explaining why a type assertion has been used, mentioning any assumptions that its safety is based on.

Also, in this case, using a type assertion to check that property doesn't narrow the type of the result variable, so this approach might not be exactly what you need.

答案2

得分: 0

你正在通过探测其值来检查.err属性是否存在。由于在TypeScript中探测不存在的属性是一个错误,而.err仅在该值上 有条件地 存在(即,仅当该值为ValidationError对象时才存在),因此会出现编译错误。

当然,如果TypeScript知道该值不仅仅是true | ValidationError,而是确切地ValidationError,那么它将不会显示任何编译错误,因为这将是.err存在的保证:

if (value === true) {
  // `value`的类型是`true`(是的,我说的是类型)
} else {
  // `value`的类型是`ValidationError`
  // `value.err`应该可以在没有编译错误的情况下访问
}
if (value instanceof Object) {
  // `value`的类型是`ValidationError`
} else {
  // `value`的类型是`true`
}
英文:

You're checking for existence of a property .err by probing its value. Since probing non-existent properties is an error in TypeScript, and .err only conditionally exists on the value (i.e., it exists only if the value is ValidationError object), you get a compilation error.

Of course, if TypeScript knows that the value is not just true | ValidationError but exactly ValidationError, then it won't show you any compilation errors, because that's going to be a guarantee that .err exists:

if (value === true) {
  // type of `value` is `true` (yes, I said type)
} else {
  // type of `value` is `ValidationError`
  // `value.err` should be accessible without compilation errors
}
if (value instanceof Object) {
  // type of `value` is `ValidationError`
} else {
  // type of `value` is `true` 
}

huangapple
  • 本文由 发表于 2023年7月18日 14:52:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76710182.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定