普通视图

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

TypeScript:为 JavaScript 注入类型安全的工程化力量

作者 Zyx2007
2026年1月24日 17:46

JavaScript 以其灵活、动态的特性成为 Web 开发的基石,但这种“自由”在大型项目中往往演变为隐患。当函数参数类型不明、对象结构随意扩展、变量用途模糊不清时,代码便如同没有护栏的悬崖——看似畅通无阻,实则危机四伏。TypeScript(TS)作为 JavaScript 的超集,通过引入静态类型系统,在保留 JS 灵活性的同时,为开发者构建起一道坚固的质量防线。

弱类型的代价:隐藏在“简单”背后的陷阱

JavaScript 是动态弱类型语言,变量类型在运行时才确定。这使得以下代码合法却危险:

function add(a, b) {
  return a + b;
}
const result = add(10, '10'); // "1010"(字符串拼接)

开发者本意是数值相加,却因传入字符串导致隐式类型转换,结果出乎意料。这类“二义性”错误在小型脚本中或许无伤大雅,但在复杂业务逻辑中,可能引发难以追踪的 bug。

TypeScript 的解法:编译期类型检查

TypeScript 通过类型注解,在代码编写和编译阶段就捕获潜在错误:

function addTs(a: number, b: number): number {
  return a + b;
}
const result2 = addTs(10, 10); // 正确
// addTs(10, '10'); // 编译报错:Argument of type 'string' is not assignable to parameter of type 'number'.

类型签名 a: number 明确约束了参数类型,编辑器会立即提示错误,无需等到运行时才发现问题。这种“早发现、早修复”的机制,极大提升了代码健壮性。

基础类型与类型推导

TS 提供丰富的内置类型,并支持类型自动推导:

let a: number = 10;
let b: string = 'hello';
let arr: number[] = [1, 2, 3];
let user: [number, string, boolean] = [1, '张三', true]; // 元组

即使省略显式注解,TS 也能根据初始值推断类型:

let count = 100; // 自动推导为 number
// count = '100'; // 错误!不能将 string 赋值给 number

这种“写得少,检查多”的体验,让开发者既能享受简洁语法,又不失类型安全。

接口与自定义类型:描述复杂结构

对于对象,TS 提供 interfacetype 来定义结构契约:

interface IUser {
  name: string;
  age: number;
  readonly id: number; // 只读属性
  hobby?: string;      // 可选属性
}

let user3: IUser = {
  name: '张三',
  age: 10,
  id: 1001
};
// user3.id = 1002; // 错误!id 是只读的

接口清晰表达了对象应具备的字段及其约束,不仅防止非法赋值,还为 IDE 提供精准的智能提示和文档查看能力。

联合类型与泛型:应对多样性与复用

TS 支持联合类型处理多可能性:

type ID = string | number;
let id: ID = 1001;
id = 'user_001'; // 合法

而泛型则实现类型级别的参数化,提升组件复用性:

let arr2: Array<string> = ['a', 'b', 'c'];
// 或简写为 string[]

泛型让函数、类、接口能适用于多种类型,同时保持类型安全,是构建通用库的核心工具。

安全的未知类型:unknown vs any

面对不确定的类型,TS 提供 unknown 作为更安全的替代方案:

let bb: unknown = 10;
bb = 'hello';
// bb.toUpperCase(); // 错误!需先类型检查
if (typeof bb === 'string') {
  console.log(bb.toUpperCase()); // 安全调用
}

相比之下,any 会完全绕过类型检查,虽可作为迁移旧代码的“救命稻草”,但应尽量避免在新项目中使用。

工程价值:不止于防错

TypeScript 的优势远超错误预防:

  • 智能提示:输入对象属性时自动补全;
  • 重构安全:重命名变量或函数时,所有引用同步更新;
  • 代码导航:一键跳转到类型定义或实现;
  • 文档内嵌:类型本身就是最好的文档;
  • 垃圾清理:未使用的变量、导入会高亮提示。

这些特性显著提升开发效率,尤其在团队协作和长期维护中,价值倍增。

用 TypeScript + React Hooks 构建一个健壮的 Todo 应用

作者 Zyx2007
2026年1月24日 17:45

最近我用 React 和 TypeScript 从零写了一个 Todo 应用,整个过程让我深刻体会到:类型系统不是束缚,而是保护。它让组件之间的协作更清晰,状态管理更可靠,连 localStorage 的读写都变得安全可控。下面分享我是怎么一步步搭建这个小项目的。

核心思路:状态集中 + 类型约束

我把所有 todo 数据和操作逻辑封装在一个自定义 Hook useTodos 里,这样组件只需要“消费”状态和方法,不用关心实现细节。同时,用 TypeScript 接口明确约定数据结构,避免传错参数或访问不存在的属性。

首先定义 Todo 的结构:

// types/todo.ts
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

这个接口就像一份契约——任何地方使用 Todo 数据,都必须包含这三个字段,且类型固定。

自定义 Hook:useTodos

这是整个应用的核心。它用 useState 管理状态,并通过 useEffect 同步到 localStorage:

// hooks/useTodos.ts
export function useTodos() {
  const [todos, setTodos] = useState<Todo[]>(() => 
    getStorage<Todo[]>(STORAGE_KEY, [])
  );

  useEffect(() => {
    setStorage<Todo[]>(STORAGE_KEY, todos);
  }, [todos]);

  const addTodo = (title: string) => {
    const newTodo: Todo = {
      id: +new Date(),
      title,
      completed: false
    };
    setTodos([...todos, newTodo]);
  };

  // toggleTodo 和 removeTodo 略...

  return { todos, addTodo, toggleTodo, removeTodo };
}

注意这里用了泛型函数 getStorage<T>setStorage<T>,确保读写 localStorage 时类型安全:

// utils/storages.ts
export function getStorage<T>(key: string, defaultValue: T): T {
  const item = localStorage.getItem(key);
  return item ? JSON.parse(item) : defaultValue;
}

这样,即使从 localStorage 读出来的数据是字符串,TS 也能正确推断为 Todo[] 类型。

组件通信:靠 Props 接口对齐

父组件 App 只负责组合,不处理逻辑:

// App.tsx
export default function App() {
  const { todos, addTodo, toggleTodo, removeTodo } = useTodos();
  return (
    <div>
      <h1>TodoList</h1>
      <TodoInput onAdd={addTodo} />
      <TodoList todos={todos} onToggle={toggleTodo} onRemove={removeTodo} />
    </div>
  );
}

子组件通过 Props 接口明确声明自己需要什么:

// components/TodoList.tsx
interface Props {
  todos: Todo[];
  onToggle: (id: number) => void;
  onRemove: (id: number) => void;
}

const TodoList: React.FC<Props> = ({ todos, onToggle, onRemove }) => {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} onToggle={onToggle} onRemove={onRemove} />
      ))}
    </ul>
  );
};

这样,如果我在 App 里不小心传了错误类型的 onToggle,TypeScript 会立刻报错,而不是等到运行时才发现按钮点不动。

单个 Todo 项:细节处理

TodoItem 中,根据 completed 状态动态设置样式:

<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
  {todo.title}
</span>

因为 todo 的类型是 Todo,所以 todo.completed 一定是布尔值,不会出现 undefined 导致样式异常。

输入框:防错处理

TodoInput 还做了空值校验:

const handleAdd = () => {
  if (!value.trim()) return; // 防止添加空任务
  onAdd(value);
  setValue('');
};

配合 TS 的 string 类型,确保传给 onAdd 的一定是字符串,不会意外传入数字或 null。

总结:为什么值得用 TS?

  • 提前暴露问题:写代码时就知道哪里传参错了;
  • 自动文档:鼠标悬停就能看到函数签名和字段说明;
  • 重构安全:改一个接口,所有用到的地方都会提示更新;
  • 团队协作友好:别人看你的组件,一眼就知道要传什么。

这个 Todo 应用虽然简单,但用 TS 写完后,感觉整个项目“稳”了很多。以后再做大项目,我肯定会首选 TypeScript —— 它不是增加负担,而是帮我们写出更干净、更可靠的代码。

❌
❌