如何在 TypeScript 中使用回调正确处理 let-variables?

2024-01-19

在使用 TypeScript 期间,我对一种常见的 JavaScript 模式有点挣扎。是关于:

  1. 声明一些“let”变量而不为其设置任何初始值
  2. 在某个回调中将此值设置为变量
  3. 执行回调后使用此变量

这是代码示例:

const wait = (cb: Function) => // just example of a possible callback
  new Promise((resolve) =>
    setTimeout(() => {
      cb();
      resolve();
    }, 1)
  );

async function v1() {
  let a: { bool: boolean };

  await wait(() => { 
    a = { bool: true }; // from sinse `a` isn't empty
  });

  alert(a); // error: Variable 'a' is used before being assigned. ts(2454)

  if (a === undefined) return; // error: Variable 'a' is used ...

  alert(a); // only now it's okay: { bool: true }
}

如你看到的:

  • TypeScript 理解这一点a可能未初始化
  • 但同时 TS 不明白它可能被初始化

好的。如果我只添加一些检查和可能性会怎样?null:

async function v2() {
  let a: { bool: boolean } | null = null;

  await wait(() => { 
    a = { bool: true };
  });

  alert(a); // no error
  alert(a.bool); // error: possibly null

  if (a === undefined || a === null) return;

  alert(a.bool); // error: Property 'bool' does not exist on type 'never' ts(2339)
}

所以现在 TypeScript 知道它不是零。所以……一定是{ bool: boolean }。但是... TypeScript 认为它是无法访问的代码分支,所以类型是never.

是否有任何简单合理的解决方案可以说服 TypeScript 代码是正确的并且类型是正确的{ bool: boolean }?

E.g.:

  • 我可以设置非空初始值。但实际上通常这是不可能的(因为有更复杂的类型)
  • 我可以用// @tsignore, as MyType, !, 忽略 linter 规则

这些 ^ 对我来说看起来不太好:) 我想我错过了一些重要的事情。

我的 tsconfig:

{
  "compilerOptions": {
    "sourceMap": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "target": "ES2019",
    "allowJs": false,
    "checkJs": false,
    "strict": true,
    "resolveJsonModule": true,
    "lib": ["ES2019", "DOM"],
    "types": ["jest", "node"],
    "typeRoots": ["./src/types", "./node_modules/@types"],
    "outDir": "./build",
    "baseUrl": "./src/",
    "paths": { "~/*": ["*"] }
  },
  ...
}

这是多种因素共同作用的结果。


主要问题是编译器不执行它的控制流分析 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#control-flow-based-type-analysis通过深入函数调用内部,找到该函数的实现,并检查任何封闭变量的状态是否已更改。这将是“正确”的行为,但对于编译器来说,为了跟踪这种状态变化而实质上模拟每个程序的运行,在时间和内存方面会非常昂贵。问题“当调用函数时,编译器应该假设其副作用是什么?”这是一个很难回答的问题,GitHub 上有一个关于这个问题的讨论:微软/TypeScript#9998 https://github.com/microsoft/TypeScript/issues/9998.

当前的编译器本质上假设函数调用具有no副作用。因此,如果a之前未设置wait()被调用后,编译器也会将其视为未设置。对于您的情况而言,情况并非如此,但在很多很多情况下,这是所需的行为。这是一个权衡。所以我们需要想出一些解决方法,让编译器处理a作为“可能设置”之后wait()被调用,而不依赖编译器为我们做这件事。


初始化的第二种方法a类似的东西null or undefined很有希望,但不幸的是您遇到了另一个问题:如果您为联合类型变量分配一个值,控制流分析将缩小变量的类型。所以之后let a: { bool: boolean } | null = null;,编译器看到a作为类型null直到且除非它被重新分配。看微软/TypeScript#8513 https://github.com/microsoft/TypeScript/issues/8513了解更多信息。这又常常是一件好事。毕竟,如果我们不尝试解决其他问题,您就会want编译器思考a is null there.

解决方法在这里(上一期中间接提到的 https://github.com/microsoft/TypeScript/issues/8513#issuecomment-217710225)可能是使用类型断言 https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions告诉编译器处理null不作为类型null,但作为联合类型{ bool: boolean } | null:

let a = null as { bool: boolean } | null;

现在你可以打电话wait(),之后推断出的类型a没有改变(由于#9998)并且仍然是联盟。然后下面的其他代码将按预期运行:

if (a === undefined || a === null) return;
alert(a.bool); // okay

那么,是吗?无论如何,它并不完美,但考虑到当前的 TypeScript 化身,它是我能建议的最好的了。无论如何,希望有帮助;祝你好运!

Playground 代码链接 https://www.typescriptlang.org/play/#code/MYewdgzgLgBA7gQwJawLwwBTAEYC4YBiArmMFEuAJQyoB8MA9AzAFZHQwCmAHggLYAHADacYIAGYwEMASAgQk2ETGAIhQ7AmABrAFAwDMMJzgwACgCcQfJBE4YMFzhBBCAbp2p19h33agAKkh8nCBEUA5e9ADePr7xKtgYlADccQmGTi7u9qnpvgC+ADQwAIyU6XkwuroIEACepDDiJGQUYM3JMLG+IrDS6GBE6lIQ3TDYIK74k66cCB0FMAA+RsNCab7pCIgo8MgRXXTd+YYD47NC+FAWRKIFm4YFeelIkhgDqOgkACac4khjD8VqtPoN1tQnFAiBYwI8DGpOBYIggAHSXVKMZggbQIeq6ApAA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 TypeScript 中使用回调正确处理 let-variables? 的相关文章

  • 在 ajax 请求上启用 jQuery contextMenu 项

    我正在尝试更新上下文菜单 http medialize github com jQuery contextMenu docs htmlitem 如果 ajax 请求改变了我的 div 内容 这就是我的意思 我有一个这样的 div div c
  • querySelector 搜索直接子级[重复]

    这个问题在这里已经有答案了 我有一些类似 jquery 的函数 function elem return gt someselector elem 问题是我怎样才能做同样的事情querySelector 问题是 gt 选择器中querySe
  • 从 puppeteer PDF 中删除分页符?

    我目前正在尝试查看是否有一种方法可以删除我的 puppeteer PDF 中的分页符 因为我当前的 PDF 设置中的一些分页符正在以一种奇怪的方式切断文本 我正在谈论的内容的屏幕截图 我的傀儡代码 app get companyId pdf
  • 如何在 Windows 网络中的 Intranet Web 应用程序中获取用户的用户名

    我内部有一个简单的 HTML 页面 它只显示一个表单并要求用户填写 我想自动捕获Windows域用户名和机器名 并将其与表单中收集的数据一起提交 我可以在客户端这样做吗 HTML JavaScript 或者我被迫在服务器端执行此操作 我还不
  • 修复 Raphaël 路径节点上 Tipsy 工具提示的位置

    这是一个非常具体且有些复杂的问题 所以我设置了一个最小测试用例 http reveal dk 8080 revealit dk tipsytest 在阅读本文的其余部分之前 您可能应该先了解一下 我的页面显示悬停时突出显示区域的图像Raph
  • 为什么这个递归函数返回未定义?

    我正在尝试编写一个使用递归组合两个字符串的函数 我的代码如下 但我不知道为什么该函数返回未定义 特别是当我在基本情况下使用 console log 时 它不会打印未定义而是打印正确的值 var str3 function merge str
  • NodeJS - 将相对路径转换为绝对路径

    In my 文件系统我的工作目录在这里 C temp a b c d 在 b bb 下有文件 tmp txt C temp a b bb tmp txt 如果我想从工作目录转到该文件 我将使用以下路径 bb tmp txt 如果该文件不存在
  • 如何将React JS状态保存到本地存储中

    我不知道如何将 React js 状态存储到本地存储中 import React Component from react import App css import auth createUserProfileDocument from
  • iPhone 上的锁定方向 UIWebView

    有没有办法锁定 UIWebView 的方向 使用 Obj C JS 还是 Html 我不想有按钮或任何东西 我只想在应用程序打开时将其锁定为纵向 好像这个堆栈溢出帖子 https stackoverflow com questions 43
  • JS:修改 JS 对象中的值/对

    我正在尝试找出修改对象的最佳方法 而无需三次写出类似的对象 所以我有这三个对象 var object1 start start end end type 1 var object2 start start end end type 2 va
  • 如何让php页面从html页面接收ajax post

    我有一个非常简单的表单 其中有一个名字输入字段 我捕获了表单数据 并使用标准 jQuery 发布方法通过 ajax 将其传输到 PHP 页面 但是 我根本无法从 PHP 页面获得任何在服务器端捕获数据的响应 我不确定我做错了什么或缺少什么
  • JavaScript setTimeout 和更改系统时间会导致问题

    我注意到如果我设置setTimeout未来1分钟 然后将我的系统时间更改为过去5分钟 setTimeout功能将在 6 分钟后触发 我这样做是因为我想看看夏令时系统时钟更改期间会发生什么 我的 JavaScript 网页使用setTimeo
  • 如何禁用网页中的萤火虫?

    如何使用 Javascript 禁用 firebug 我想这样做是为了向访问者隐藏我的网页的运作方式 有什么选择可以做到这一点吗 你不能 你能做的最好的事情就是混淆你的 JavaScript 实际上刮掉了 您能做的最好的事情就是将所有安全关
  • WebpackError:ReferenceError:Gatsby 上未定义窗口

    我已经在互联网上进行了大量搜索 但无法解决这个问题 我正在使用 Gasby 开发静态页面 但遇到此错误 WebpackError ReferenceError window is not defined 我的线索是 这与我正在使用的引导 模
  • 如何捕获文本区域上的 Enter 按键而不是 Shift+Enter? [复制]

    这个问题在这里已经有答案了 I m doing it for texarea A function should be called when the user press Enter but nothing should be done
  • 获取类中的所有静态 getter

    假设我有这个类 我像枚举一样使用它 class Color static get Red return 0 static get Black return 1 有没有类似的东西Object keys to get Red Black 我使用
  • 如何计算一行中Flexbox项目的数量?

    网格是使用 CSS flexbox 实现的 Example http jsbin com jumosicasi edit html css js output 本示例中的行数为 4 因为我出于演示目的固定了容器宽度 但是 实际上 它可以根据
  • 有序 JSON 对象

    我有一个 servlet 它与数据库通信 然后返回有序 按时间排序 对象的列表 在servlet部分 我有 access DB returns a list of User objects ordered ArrayList users M
  • 从json中获取所有子节点

    我有以下 json var source k 01 k 02 children k 05 k 06 children k ABC k PQR k 07 k 03 我希望能够指定 k 的值并取回所有孩子 以及孙
  • 在引导程序中以编程方式更改选项卡窗格选项卡

    我使用的选项卡窗格定义为 ul class nav nav tabs li a href personal Personal Information a li li class active a href contact Contact a

随机推荐

  • Dart 将列表转换为 JSON 编码的映射条目

    我问了一个question https stackoverflow com questions 25615241 dart json decoder然而 在关于 Dart 编码 解码到 JSON 之前 建议的库并不完整 我决定手动处理它 目
  • Python:使用 pre 和 post 方法包装方法调用

    我正在实例化一个 A 类 我从某人那里导入 否则 所以我无法修改它 到我的 X 类中 有没有办法可以拦截或包装对 A 中方法的调用 即 在下面的代码中我可以调用 x a p1 并得到输出 X pre A p1 X post 很多TIA cl
  • WinDbg - 将字符串参数与内存中的字符串进行比较

    我需要比较一个字符串 作为参数传递给WinDbg用记忆中的字符串 如何才能实现这一目标 例如 该字符串位于加载的 PE 内的特定偏移处 所以 我可以通过执行轻松读取字符串da c 100
  • 通过python将PPT转换为PNG

    我想使用 Python 将 PPT 转换为 png 或其他图像格式 这个问题已经在 SO 上被问过 但本质上建议在无头 X 服务器中运行 OpenOffice 这在我上次使用它时绝对是痛苦的 主要是由于 OO 崩溃而导致难以复制的错误 还有
  • 构建后事件始终在 MSBUILD VS2013 中运行

    这是非常类似于这个问题 https stackoverflow com questions 4185638 solving the visual studio 2010 alwayscreate rebuild issue 除了我修复VS2
  • 使用 Taglib-Ruby 为 Web 应用程序构建自定义 Heroku Ruby/Rails Buildpack

    我已经使用 Rails 3 2 构建了一个应用程序 该应用程序利用了 taglib ruby gem 我需要将此应用程序上传到 Heroku 但它无法成功构建 taglib ruby gem 因为需要在计算机上安装关联的 C taglib
  • Open 和 Closed 仅仅是 ConnectionState 枚举的相关值吗?

    The MSDN 页面 http msdn microsoft com en us library system data connectionstate aspxConnectionState 枚举表示 此值是为产品的未来版本保留的 适用
  • 运行 Maven 集成测试时出现“在分支目标处期望堆栈图框架”

    我正在使用 Maven 3 2 3 和这个版本的 Java davea echo JAVA HOME Library Java JavaVirtualMachines jdk1 8 0 45 jdk Contents Home 当我跑步时
  • 高级查找 - 用 OR 组合相关实体

    有什么方法可以创建该查询吗 我需要来自地址和联系地址的数据 通常我可以将它们组合起来Combine OR但在本例中则不然 我想我必须编写新插件PreExecute 方法 获取我的查询 解析数据 然后手动获取相等的地址或者还有其他方法吗 我不
  • 在 .NET 中上传大文件

    我做了很多研究来找到一个 NET 的上传组件 我可以用它来上传大文件 有一个进度条 并且可以恢复大文件的上传 我遇到过一些组件 例如Ajax上传器 http ajaxuploader com 流畅上传 http krystalware co
  • 如何让span占据包含td的整个高度

    我有一个表 在左列中我想为该行添加一个指示器 我使用跨度来渲染指示器 但无法让跨度占据整个高度 table tr td style padding 0px span style height 100 width 5px background
  • 触发 onactivatefile 时向预览图像添加图标/div (Filepond)

    背景 我有一个 django 项目 用户可以上传多个图像 其中一个将是主图像 我正在使用 Filepond 上传和优化图像 Filepond 上传文件的顺序并不总是与用户选择的文件的顺序相同 因此 我尝试为用户提供一个选项 用户单击文件 在
  • Jasmine 在单独的测试项目中

    将 jasmine 测试分离到单独的 Visual Studio 项目中是否实用 可能 我刚刚开始使用角度 并尝试在开始实际的角度实现之前编写我的测试 我将写我的项目视觉工作室2012与厚颜无耻的测试运行者 看这个video http ww
  • iPhone sqlite 应用程序可以附加到其他数据库吗?

    ATTACH DATABASE 命令对于在 sqlite 数据库文件之间传输行非常有用 并允许您跨数据库连接表中的行 例如 sqlite3 BookLoansDB sqlite sqlite gt ATTACH DATABASE Users
  • Android - 使用图像资源背景动态构建 UI 时出现内存泄漏

    我有一个活动 我发誓正在泄漏内存 我正在开发的应用程序需要处理大量图像 因此在直接使用位图时 我必须非常吝惜内存 我添加了一个 Activity 现在如果您使用这个新 Activity 它基本上会让我处于内存使用的边缘 最终抛出 位图超出
  • Ubuntu 中的 .m2 、settings.xml [重复]

    这个问题在这里已经有答案了 在 Windows 环境中 您将在 C Users user name 位置中有 m2 文件夹 您将把 settings xml 文件复制到其中 以便设置代理设置和 Nexus 存储库位置等 那么我必须在 Ubu
  • 多个 CALayers 动画 - 填充模式

    我将屏幕分成小块 然后为每个块设置动画以执行转换 for int x 0 x
  • javascript能区分左移键和右移键吗?

    大多数情况下 这是一个健全性检查 两个 Shift 键的键码都是 16 这是否意味着浏览器中实际上无法区分左移和右移事件 在较新的浏览器中支持DOM3您可以使用event location检查位置 In DOM3 规范 http www w
  • 安装和导入 SendKeys 时出现问题 -- 更新

    我正在尝试安装适用于 Python 的 SendKeys If I use pip install SendKeys 我收到链接错误 sendkeys c 150 warning C4013 Py InitModule undefined
  • 如何在 TypeScript 中使用回调正确处理 let-variables?

    在使用 TypeScript 期间 我对一种常见的 JavaScript 模式有点挣扎 是关于 声明一些 let 变量而不为其设置任何初始值 在某个回调中将此值设置为变量 执行回调后使用此变量 这是代码示例 const wait cb Fu