普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月6日首页

告别 `any`:TypeScript 中 `try...catch` 的最佳实践

作者 ssshooter
2026年4月6日 12:42

在 TypeScript 项目中,你是否经常为了通过编译而写出这种代码?

try {
  // 某些逻辑
} catch (err: any) { // ❌ 违背了 TS 类型安全的初衷
  console.log(err.message); 
}

随着 TS 配置趋于严格,catch(err: any) 往往会触发 ESLint 警告或编译错误。本文将介绍处理 catch 块中错误对象的几种最佳实践

1. 理解 unknown 的必然性

在现代 TypeScript(4.0+)中,推荐将捕获到的错误声明为 unknown。这是因为在运行时刻,你无法保证捕获到的一定是 Error 实例。

try {
  throw "意外的错误字符串"; // 这里的错误甚至不是一个对象
} catch (err: unknown) {
  // ❌ 报错:'err' is of type 'unknown'
  // console.log(err.message); 
}

2. 方案一:类型守卫(Type Guards)—— 最稳健的方法

这是官方推荐的做法。通过显式的 instanceof 检查,TS 会在代码块内自动收窄(Narrowing)类型。

try {
  await fetchData();
} catch (err: unknown) {
  if (err instanceof Error) {
    // ✅ TS 现在知道 err 是 Error 类型
    console.error(err.message);
    console.error(err.stack);
  } else {
    // 处理非标准错误(如 throw "string")
    console.error("发生了未知类型的错误", err);
  }
}

3. 方案二:自定义工具函数(封装大法)

如果你觉得到处写 if (err instanceof Error) 太麻烦,可以封装一个工具函数。这是目前大型项目中最流行的做法。

编写工具函数

function toError(err: unknown): Error {
  if (err instanceof Error) return err;
  return new Error(String(err));
}

业务中使用

try {
  doSomething();
} catch (err: unknown) {
  const error = toError(err);
  console.log(error.message); // ✅ 永远安全
}

4. 方案三:函数式处理(类似 Rust/Go)

如果你讨厌深层嵌套的 try...catch,可以使用封装好的包装器,将错误作为返回值返回。

async function safeRun<T>(promise: Promise<T>): Promise<[Error | null, T | null]> {
  try {
    const data = await promise;
    return [null, data];
  } catch (err: unknown) {
    return [toError(err), null];
  }
}

// 使用:
const [err, data] = await safeRun(fetchUser(id));
if (err) {
  handle(err);
} else {
  render(data);
}

5. 进阶:处理 Axios 等库的特定错误

如果你在使用 Axios,可以使用它内置的类型守卫:

import axios from 'axios';

try {
  await axios.get('/api/user');
} catch (err: unknown) {
  if (axios.isAxiosError(err)) {
    // 这里可以访问 err.response, err.status 等特有属性
    console.log(err.response?.data);
  }
}

总结:该选哪一个?

场景 推荐做法
临时处理/小型脚本 if (err instanceof Error)
标准大型项目 封装 toError() 工具函数,确保类型安全
追求代码扁平化 采用 safeRun 包装器返回 [err, data]
第三方库请求 优先使用库提供的 isError 判断函数

核心原则: 永远不要相信 catch 捕获到的内容,永远在访问属性前进行类型检查。这不仅是过编译的要求,更是写出健壮代码的基石。

昨天以前首页

Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑

作者 ssshooter
2026年3月30日 23:06

很多开发者在把 Tauri 2 应用上架到 iOS(真机或模拟器)时,都会在文件保存这一步踩坑:明明代码代码在其他平台没问题,在 iOS 路径就返回 null,或者在「文件」App 里根本看不到自己的 App 文件夹。

下面我把最常见的几个坑总结成一份避坑科普文,帮你一次性避开这些“iOS 特色”问题。

坑 1:@tauri-apps/plugin-dialogsave() 在 iOS 上经常返回 null 或路径不可用

现象
调用 const path = await save({...}) 后,一个 0KB 的文件写入成功,但是 path 返回是 null

避坑方法

  • 不要过度依赖 dialog.save() 来实现“用户任意选择保存位置”。
  • 优先使用 直接写入 App 的 Documents 目录(见坑 3)。
  • capabilities 中确保开启 dialog:save 权限。

坑 2:文件明明写入了,但「文件」App 里完全看不到 “Mind Elixir” 文件夹

现象: 用了 BaseDirectory.Document 保存文件后,在「文件」App → 浏览 → On My iPhone 里找不到你的 App 文件夹。

原因: iOS 沙盒机制严格控制 App 的 Documents 目录是否对「文件」App 可见。Tauri 默认生成的 iOS 项目不会自动添加暴露文件夹的配置,就算你写再多文件,文件夹也不会出现。

避坑方法(最关键的一步): 在 Info.plist 中添加以下两个 key(必须同时添加):

<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

位置:通常在 src-tauri/gen/apple/ios/App/App/Info.plist(或你的项目对应路径),加在 <dict> 标签内,</dict> 之前。

添加后必须重新构建并安装 Appcargo tauri ios build 或用 Xcode 编译),然后:

  • 先执行一次写入操作(创建文件)。
  • 完全退出「文件」App(上滑关闭),重新打开并下拉刷新「On My iPhone」。

此时你应该能看到和 App 同名的文件夹(显示名称来自 productName 或 Xcode Display Name)。

注意:这两个 key 只控制可见性,不影响代码读写。

坑 3:iOS 上最好的保存方式其实不是 dialog,而是直接用 Documents 目录

推荐做法

import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'

await writeTextFile('my-note.md', '你的内容...', {
  dir: BaseDirectory.Document
})

优点:

  • 最稳定,几乎不会出现 0 字节文件的问题。
  • 用户可以在「文件」App 里直接看到和管理文件(添加上面两个 plist key 后)。
  • 无需处理复杂的 URI 和潜在的 fs bug。

如果你想让用户输入文件名,可以结合 prompt 或自定义输入框实现。

总结建议

在 Tauri 2 + iOS 开发中:

  1. 优先使用 BaseDirectory.Document 直接保存(最稳)。
  2. 必须在 Info.plist 添加 UIFileSharingEnabledLSSupportsOpeningDocumentsInPlace
  3. 谨慎使用 dialog.save() + writeFile,因为移动端兼容性还有待完善(官方 issue 仍在跟进)。
  4. 开发时多用控制台日志 + Safari/XCode 调试,遇到路径问题先检查 plist 和权限。

避开这几个坑后,你的 Mind Elixir(或其他 App)在 iOS 上的文件保存功能就会顺畅很多。iOS 的沙盒和文件系统规则和桌面差异很大,提前了解这些“Apple 特色”能省下大量调试时间。

(本文基于 Tauri 2 常见 issue 和实际开发经验总结,iOS 规则可能随系统版本微调,建议以 Apple 官方文档为准。)

❌
❌