TypeScript 建议:可选的全局变量

hpxqektj  于 8个月前  发布在  TypeScript
关注(0)|答案(6)|浏览(95)

搜索词

建议

从相关问题中提取:#21965
目前,TypeScript没有表示可能在运行时存在或不存在的全局变量的方法。
例如,给定一个名为foo的全局变量,我们只能将其定义为:

declare global {
  const foo: number;
}

但是,如果foo并不总是存在呢?
我们不能将其定义为T | undefined,因为这并不反映运行时的行为。T | undefined表示“这将被声明,但其值可能是undefined”,但全局变量可能根本不会被声明,在这种情况下,任何引用都会导致运行时的异常。

declare global {
  const foo: number | undefined;
}
// TypeScript is happy for us to reference this. There's no compile error.
// The type is `number | undefined`.
// At runtime, if the global doesn't exist, we'll get a `ReferenceError` 😒
foo;

如果有一种方法可以将全局变量标记为可选,那么在安全访问之前,TypeScript需要一个合适的保护(typeof foo !== 'undefined')。这样,TypeScript就提醒我们可能会出现运行时异常。

declare global {
  // example syntax
  const foo?: number;
}
// Compile error 😇
// Runtime exception 😇
foo;

if (typeof foo !== 'undefined') {
  // No compile error 😇
  // No runtime exception 😇
  // Type is `number`
  foo;
}

用例

以下是“可选全局变量”的示例。
在客户端(浏览器)和服务器(Node)之间共享的代码中:

  • window 仅在客户端运行时存在
typeof window !== undefined ? window.alert('yay') : undefined;
  • globalrequire 仅在服务器运行时存在
typeof process !== undefined ? process.env.FOO : undefined;

在可能使用 Cypress 进行测试的代码中,当代码处于测试状态时,全局变量 Cypress 仅在运行时存在。

const getEnvVar = key => typeof Cypress !== 'undefined' ? Cypress.env(key) : process.env[key];

示例

如上所述。

检查清单

我的建议满足以下指导原则:

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

wribegjk1#

在每个环境中(浏览器/Node),全局变量要么总是存在,要么总是不存在。从这个意义上说,“可选”这个词可能并不完全合适。我很想提出更好的DX人体工程学,但作为反面观点,我们不应该建议为每个环境设置单独的tsconfig,并为每个环境进行单独的类型检查吗?这不需要TS端的更改:一个--noEmit运行Node,然后使用emit运行浏览器。虽然有点繁琐,但同时,它是一种公平且准确的分离方式。

ttisahbt

ttisahbt2#

关于编辑器体验,VS Code(例如)需要选择要使用的TS配置。你可能可以使用项目引用将多个项目组合成一个,但我仍然不确定编辑器体验是否符合我们的需求,如上例所示:#36057(评论)。如果我正在查看一个在Node和浏览器中使用的代码文件,尝试使用window / global时应该会出错,但一旦添加了一个保护措施,错误就应该消失了。

b4wnujal

b4wnujal3#

好的点子,编辑经验应该是直截了当的。

1szpjjfi

1szpjjfi4#

单独的tsconfig无法解决这个问题。比如我写了一个类似这样的函数:

export function lowByte(value: number|bigint|BigInt): number {
    if (typeof BigInt !== 'undefined' && (typeof value === 'bigint' || value instanceof BigInt)) {
        return Number(BigInt.asUintN(8, value as bigint));
    } else {
        return (value as number & 0xff);
    }
}

这个函数旨在在所有浏览器上工作,无论它们是否具有 BigInt
在这里使用 BigInt 的所有用法都是安全的,因为它们受到 BigInt 存在性测试的保护。
为了进行类型检查,我需要 BigInt 的声明在作用域内。
然而,添加 lib.es2020.bigint 会影响整个编译过程。任何其他代码都可以在没有存在保护的情况下使用 BigInt ,它将通过类型检查但在运行时会抛出异常。
我没有找到阻止 BigInt 影响整个编译过程的方法,除了在使用它的模块中本地复制 BigInt 的typedef。当可选的全局变量具有复杂的接口(如 Promise )时,这会变得很丑陋。

x7rlezfr

x7rlezfr5#

另一些需要考虑的事情:除了将单个全局变量标记为可选之外,我们可能还需要一种方法来指定一组全局变量为可选。
在客户端/服务器之间共享的代码示例中:

  • dom 库内的所有全局变量必须是可选的(即 Window )
  • @types/node 类型内的所有全局变量必须是可选的(即 NodeJS.Global )

理想情况下,隐式全局类型将像带标签的联合体一样工作:

Global = Window | NodeJS.Global

... 并且检查联合体中属性的存在将缩小隐式全局类型,以便其他属性可以在没有额外保护的情况下访问:

window; // Compile error 😇 
alert; // Compile error 😇 
addEventListener; // Compile error 😇 

if (type window !== 'undefined') {
  window; // No compile error 😇 

  // We checked for the existence of `window`, so other globals must also exist now
  alert('foo'); // No compile error 😇 
  addEventListener('load', () => {}); // No compile error 😇
}

与此同时,我通过定义别名来模拟这种方式:

type Env = typeof window | NodeJS.Global;

const hasWindow = typeof window !== 'undefined';

const env: Env = hasWindow ? window : global;

const checkIsEnvWindow = (_thisEnv: Env): _thisEnv is typeof window => hasWindow;

env.alert; // Compile error 😇
env.addEventListener; // Compile error 😇

if (checkIsEnvWindow(env)) {
  env.alert('foo'); // No compile error 😇
  env.addEventListener('load', () => {}); // No compile error 😇
}

这种方法的缺点是它依赖于纪律——任何人仍然可以引用/使用 windowglobal 类型的引用/使用。

pod7payv

pod7payv6#

我认为这是一个非常相关的功能,因为越来越多的人开始编写通用代码。但实际上为讨论做出贡献:
我认为这不应该被视为关于全局变量的,而是关于在任何模块或全局变量上合并声明。
想象一下我修改了一个Node.js核心模块的类型定义,因为它有一个实验性的API,或者有一些东西附加到它上面,但我不能假设它存在,而且还需要支持“普通”的Node.js。
从概念上讲,我希望我的整个declare块是“可选的”,意味着它里面的每个属性都必须成为原始模块上的属性和应用声明后的属性的并集。
我对TS代码库不太了解,意识到这可能比在一个declare global块中实现普通的可选属性更难,但从类型系统的Angular 来看似乎是可行的,我相信“可选声明块”可以是一个相对容易使用的解决方案。

相关问题