TypeScript 允许在函数参数之外缩小类型和值的范围,

wvt8vs2t  于 7个月前  发布在  TypeScript
关注(0)|答案(1)|浏览(63)

建议

🔍 搜索词

缩小全球类型Assert函数类型 predicate

✅ 可实现性检查清单

我的建议满足以下准则:

  • 这不会对现有的TypeScript/JavaScript代码造成破坏性的改变
  • 这不会改变现有JavaScript代码的运行时行为
  • 这可以在不根据表达式的类型发出不同的JS的情况下实现
  • 这不是一个运行时特性(例如库功能、JavaScript输出的非ECMAScript语法、JS的新语法糖等)
  • 这个特性将与TypeScript's Design Goals的其他部分一致。

⭐ 建议

允许Assert函数(和类型 predicate 函数)做以下事情:

  • 更改函数参数之外的值的类型
  • 扩展全局声明的接口

📃 动机示例

对于原型扩展等场景非常有用,因为在这种情况下,你不得不定义新属性的类型两次:

declare global {
    interface String {
        foo: (value: string) => string
    }
}
Object.defineProperty(String.prototype, 'foo', {
  value: (value: string) => value,
  enumerable: false,
})

相反,你可以定义一个 Package 函数来完成这两件事:

export function defineProperty<T, Key extends string | number | symbol, Value>(
  object: T,
  key: Key,
  value: Value
): asserts InstanceType<T> /* this can now be either a value or a type*/ is T & { [K in Key]: Value } {
  Object.defineProperty(object, key, { value, enumerable: false })
}

defineProperty(String.prototype, 'foo', (value: string) => value);

['hi'].foo('value')

💻 使用案例

对于cypress这样的工具,当你需要添加新函数时,你不得不定义它们的签名两次(参见https://docs.cypress.io/api/cypress-api/custom-commands)

declare global {
    interface Chainable {
        foo(bar: number): string
    }
}

Cypress.Commands.add('foo', (bar: number) => "foo")

相反,你可以使用Assert函数同时定义两者。

//(pseudocode, the actual function signature to make this accurate to how commands work in cypress is like 60 lines long)
function addCommand<Name extends string, Func extends (...args: any[]) => any>(
  name: Name,
  func: Func
): asserts Chainable is Chainable & { [Key in Name]: Func } /* new syntax */ {
  Cypress.Commands.add(name, func)
}

//add the new command
addCommand(cy, 'foo', (bar: number) => "foo")

//use the new command
cy.foo(1)

目前,你可以这样做:

function addCommand<Name extends string, Func extends (...args: any[]) => any>(
  _cy: typeof cy, //need to pass the unused cy object in order for the function to be able to change its type
  name: Name,
  func: Func
): asserts _cy is typeof _cy & { [Key in Name]: Func } {
  Cypress.Commands.add(name, func)
}

当前方法的问题:

  • 你必须无谓地将全局cy对象传递给函数,否则其类型无法Assert
  • 只有cyChainable示例被缩小,而不是Chainable接口本身,这意味着如果你将其链接到之前的命令上,你不能使用这个新方法。
cy.get('foo').foo(1) //error: property 'foo' doesn't exist on Chainable
qlzsbp2j

qlzsbp2j1#

这是一个使用案例:
我有一个名为 _expect 的函数,它返回 { toHaveProperty() }。这样我就可以执行类似 _expect(globalThis).toHaveProperty('hasOwnProperty')

const _expect = (object: any) => {
   return {
      toHaveProperty<T extends PropertyKey>(requiredProperty: T): asserts object is Record<T, unknown> {
         if (!(requiredProperty in object)) {
            throw TypeError(`Required property ${String(requiredProperty)} is missing`)
         }
      }
   }
}

的操作。但是这里有一个错误,因为 toHaveProperty 没有 object 作为参数。
参考链接:https://www.typescriptlang.org/play?#code/MYewdgzgLgBA+gUwB4AcHFgXhgChAIwCt0oAuGAQzAE8BKGTAPhgG8BYAKBm4CcEoArjzCtO3cTCggAEhQBuCAAo8QaHlGoAeACoxkUBGAAmEGMtUJ11ANIJqjHHwCOAgJZ8j5tRvLba5CggISyhTAmIMGFdTACV0EB4jHQAaGAEwAGswEAB3MGZ2LgkJVwAzXABCRwQXdwRPFW9qKJFwklp6QuLuyQALFRyYbWo0AFEeFR4cAAM42o8YFEaQ5oASFgBlKB5XMABzavn6rxXaAF8o0wBbaIhdvenaMR6YM+eJN6LXzjOgA

相关问题