学了TypeScript却用不起来?用JSDoc在JavaScript中立即学以致用

112 阅读7分钟

告别"学完就忘",今天学今天用,真正掌握类型思维

一个真实的故事

上周和一位工作4年的前端朋友吃饭,他一脸沮丧地说:

"去年花了两周学TypeScript,每个概念都懂了。但公司项目全是JavaScript,现在只记得interfacetype这两个词,写代码时完全用不上那些类型思维。"

这不是他一个人的困境。我见过太多JavaScript开发者陷入这个循环:

学习TypeScript → 没有项目可以实践 → 知识逐渐遗忘 → 需要时重新学习

更扎心的是,即使你跳槽到了用TypeScript的公司,面对真实的复杂业务逻辑,你还是不知道怎么把类型系统用好——因为你没有在真实项目中培养过类型思维

根本问题:学习与实践脱节

传统的TypeScript学习路径有个致命缺陷:它需要你有一个TypeScript项目才能实践

但现实是:

  1. 你维护的老项目是JavaScript,重构成TypeScript风险太大
  2. 新项目不敢轻易用TypeScript,怕团队不适应
  3. 练习项目太简单,学不到真实业务中的类型设计

所以,你学到的泛型联合类型类型守卫这些概念,都成了"屠龙技"——理论完美,无处施展。

突破口:在JavaScript中实践TypeScript思维

如果我告诉你,不需要改变任何项目配置,不需要说服团队,今天就可以在你现有的JavaScript项目中实践TypeScript的核心思想,你相信吗?

秘密武器就是:JSDoc注释

这不是"写注释",这是"类型编程"

看一个简单的例子:

// 之前:模糊的函数,调用时得猜参数
function calculateDiscount(price, discount) {
  return price * (1 - discount);
}

// 之后:明确的契约,IDE提供完整智能提示
/**
 * 计算商品折扣后价格
 * @param {number} price - 原价(必须大于0)
 * @param {number} discount - 折扣率(0-1之间的小数)
 * @returns {number} 折扣后价格
 * @throws {Error} 当价格小于等于0或折扣率不在0-1之间时抛出错误
 */
function calculateDiscount(price, discount) {
  if (price <= 0) throw new Error('价格必须大于0');
  if (discount < 0 || discount > 1) throw new Error('折扣率必须在0-1之间');
  return price * (1 - discount);
}

你获得了什么?

  1. 智能提示:调用时看到参数名、类型、描述
  2. 错误预防:传入错误类型时IDE立即提醒
  3. 代码即文档:不需要另外写API文档
  4. 重构安全网:修改函数时,类型不匹配会报警

你付出了什么?

  • 写注释的30秒时间
  • 零配置、零构建更改、零团队沟通成本

为什么这是最佳学习路径?

1. 即时反馈的学习闭环

传统的学习:

看书/视频 → 做练习题 → 等待真实项目 → 半年后忘了

用JSDoc的学习:

学习一个类型概念 → 在现有JavaScript项目中使用 → 立即获得IDE反馈 → 巩固理解

今天学了泛型,今天就在工具函数中用@template实践,这才是有效的学习。

2. 培养类型思维,而非记忆语法

TypeScript的核心价值不是记住typeinterface的区别,而是培养类型思维

  • 契约思维:函数/模块的输入输出要明确
  • 边界思维:数据在不同模块间流动时的类型变化
  • 安全思维:如何在编码阶段预防undefined is not a function

JSDoc让你在真实业务代码中培养这些思维,而不是在练习题里。

3. 为未来打下真正基础

当你通过JSDoc熟悉了类型系统后:

  • 接手TypeScript项目时,你能快速理解设计思路
  • 设计新系统时,你能自然想到类型约束
  • 面试被问类型系统时,你有真实项目经验可讲

这才是"学以致用"的真正含义。

立即开始:3个层次实践指南

层次1:基础函数类型(今天就能用)

从你正在写的下一个工具函数开始:

// 你的旧函数
function formatName(firstName, lastName, middleName) {
  if (middleName) {
    return `${lastName} ${firstName} ${middleName}`;
  }
  return `${lastName} ${firstName}`;
}

// 加上JSDoc后
/**
 * 格式化中文姓名
 * @param {string} firstName - 名
 * @param {string} lastName - 姓
 * @param {string} [middleName] - 中间名(可选)
 * @returns {string} 格式化后的姓名
 */
function formatName(firstName, lastName, middleName) {
  if (middleName) {
    return `${lastName} ${firstName} ${middleName}`;
  }
  return `${lastName} ${firstName}`;
}

// 调用时获得智能提示
// 输入 formatName( 会提示:
// firstName: string, lastName: string, middleName?: string
const name = formatName('三', '张', '小'); // ✅ 正确
const error = formatName('三', 123); // ❌ IDE会提示类型错误

层次2:复杂数据类型(一周内掌握)

当你需要处理复杂对象时,使用@typedef

/**
 * 用户信息类型
 * @typedef {Object} User
 * @property {string} id - 用户ID
 * @property {string} name - 用户名
 * @property {number} [age] - 年龄(可选)
 * @property {'active'|'inactive'|'suspended'} status - 用户状态
 */

/**
 * 用户列表响应类型
 * @typedef {Object} UserListResponse
 * @property {User[]} data - 用户列表
 * @property {number} total - 总数
 * @property {number} page - 当前页码
 */

/**
 * 获取用户列表
 * @param {Object} params - 查询参数
 * @param {number} params.page - 页码
 * @param {number} params.pageSize - 每页数量
 * @param {string} [params.keyword] - 搜索关键词
 * @returns {Promise<UserListResponse>}
 */
async function fetchUsers(params) {
  // 这里 params 有完整的类型提示
  const { page, pageSize, keyword } = params;
  
  // 模拟API调用
  const response = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(params)
  });
  
  /** @type {UserListResponse} */
  const result = await response.json();
  return result;
}

// 使用时获得完整类型支持
const response = await fetchUsers({ page: 1, pageSize: 20 });
// response.data 是 User[] 类型,有完整智能提示
const firstUser = response.data[0];
console.log(firstUser.id);   // ✅ 正确
console.log(firstUser.email); // ❌ IDE提示:User类型没有email属性

层次3:泛型实践(一个月内精通)

当你需要编写灵活的工具函数时,尝试泛型:

/**
 * 从数组中查找符合条件的元素
 * @template T
 * @param {T[]} array - 要搜索的数组
 * @param {(item: T) => boolean} predicate - 判断函数
 * @returns {T | undefined} 找到的元素或undefined
 */
function findItem(array, predicate) {
  return array.find(predicate);
}

// 使用示例
const users = [
  { id: '1', name: '张三', age: 25 },
  { id: '2', name: '李四', age: 30 }
];

const result = findItem(users, (user) => user.age > 28);
// result 被推断为 { id: string, name: string, age: number } | undefined

// 泛型也能用在类中
/**
 * 简单的数据存储类
 * @template T
 */
class SimpleStore {
  /**
   * @param {string} key - 存储键名
   * @param {T} initialValue - 初始值
   */
  constructor(key, initialValue) {
    this.key = key;
    /** @private @type {T} */
    this._value = initialValue;
  }
  
  /**
   * 获取当前值
   * @returns {T}
   */
  get() {
    return this._value;
  }
  
  /**
   * 设置新值
   * @param {T} newValue
   */
  set(newValue) {
    this._value = newValue;
  }
}

// 使用时获得类型安全
/** @type {SimpleStore<User>} */
const userStore = new SimpleStore('currentUser', { id: '1', name: '张三' });

const user = userStore.get(); // User类型
userStore.set({ id: '2', name: '李四' }); // ✅ 正确
userStore.set({ id: '3' }); // ❌ 缺少name属性,IDE会提示错误

配置你的开发环境

要让JSDoc发挥最大作用,只需要一个简单的配置文件:

// 在项目根目录创建 jsconfig.json
{
  "compilerOptions": {
    "checkJs": true,     // 启用对.js文件的类型检查
    "module": "ESNext",   // 根据你的项目选择
    "target": "ES2020",   // 根据你的项目选择
    "lib": ["ES2020", "DOM"] // 添加需要的库类型
  },
  "include": ["src/**/*"], // 检查哪些文件
  "exclude": ["node_modules", "dist"] // 排除哪些文件
}

保存这个文件后,VSCode或WebStorm会自动开始对你的JavaScript文件进行类型检查。

常见疑问解答

Q:这和我直接用TypeScript有什么区别? A:最大的区别是零迁移成本。你可以继续使用现有构建工具、保持现有文件结构、不需要团队统一意见。这是纯粹的"个人效率提升工具"。

Q:学习JSDoc会影响我学TypeScript吗? A:正好相反。JSDoc的类型系统是TypeScript的子集,你学的每个概念(泛型、联合类型、接口等)都能直接对应到TypeScript。这是在JavaScript环境中学习TypeScript思想。

Q:我需要记住很多JSDoc标签吗? A:不需要。从最常用的三个开始:@param@returns@typedef。80%的场景这三个就够了。其他标签用到时查一下就行。

Q:团队其他人不用JSDoc,会影响我吗? A:完全不影响。JSDoc是注释,其他人不写,你的代码依然能运行。只是调用他们的函数时没有类型提示而已。

今日行动:开始你的第一个JSDoc实践

任务:找到你今天或昨天写的3个JavaScript函数,为它们添加JSDoc注释。

步骤

  1. 打开你的项目
  2. 找一个工具函数或工具类
  3. 花1分钟添加@param@returns
  4. 保存文件,看看IDE的智能提示变化

示例起点

// 找一个像这样的函数开始
function processOrder(order, options) {
  // 你的代码...
}

// 加上:
/**
 * 处理订单
 * @param {Object} order - 订单对象
 * @param {string} order.id - 订单ID
 * @param {number} order.amount - 订单金额
 * @param {Object} [options] - 处理选项
 * @returns {Promise<boolean>} 处理是否成功
 */

下篇预告

在下一篇文章中,我将从技术Leader的视角分享:

  • 如何在不引发团队抵触的情况下推广类型思维
  • JSDoc如何作为团队代码质量的"隐形护栏"
  • 为什么"培养类型思维"比"强制迁移TypeScript"更有长期价值

今日挑战:尝试为你最复杂的一个业务函数添加完整的JSDoc注释,感受类型思维如何帮你理清函数边界。

你有过"学了TypeScript却无处用"的经历吗?或者已经尝试过JSDoc注释?欢迎在评论区分享你的故事!

如果觉得这篇文章有帮助,请点赞收藏,让更多需要的人看到。