基础实战:TypeScript Todo应用

本实战项目旨在通过纯TypeScript实现一个具备本地存储功能的Todo应用,帮助开发者掌握TypeScript在前端基础开发中的核心应用,包括类型定义、DOM操作类型处理、本地存储数据类型约束等关键技能。

需求分析

从功能与交互层面明确应用边界,确保开发目标清晰:

  • 核心功能列表:实现任务的完整生命周期管理,包括添加新任务、标记任务完成状态、删除任务,以及通过本地存储实现任务数据的持久化保存与读取。
  • UI设计规划:采用简洁直观的界面布局,主要包含任务输入框(用于输入新任务内容)、任务列表区域(展示所有任务及状态)、状态筛选控件(支持按完成/未完成状态筛选任务)。

功能优先级:本地存储功能需确保数据可靠性,DOM操作需处理类型安全,任务状态更新需实时反馈至UI,形成"输入-存储-渲染"的完整闭环。

技术准备

搭建基础开发环境与技术框架,为TypeScript开发提供支持:

  • HTML结构:构建基础页面骨架,包含id为"input"的任务输入框、id为"todoList"的任务列表容器、以及筛选按钮等核心DOM元素。
  • TypeScript配置:在tsconfig.json中设置关键编译选项,确保代码兼容性与输出控制:
    • target: ES6:指定编译目标为ES6,支持现代JavaScript特性
    • module: ES6:采用ES6模块系统,便于代码拆分与导入
    • outDir: dist:将编译后的JavaScript文件统一输出至dist目录
  • DOM类型定义:熟悉TypeScript内置的DOM类型系统,如HTMLElement(通用元素类型)、HTMLInputElement(输入框元素类型)、HTMLButtonElement(按钮元素类型)等,为DOM操作提供类型约束。

分步实现

采用模块化思想拆分代码,实现高内聚低耦合的应用架构:

1. 核心类型定义
首先定义任务数据的基础结构,通过interface明确Todo对象的类型约束,确保数据格式一致性:

typescript
复制代码
// 定义任务数据结构
interface Todo {
  id: string;       // 任务唯一标识,可通过Date.now().toString()生成
  text: string;     // 任务描述文本
  completed: boolean; // 任务完成状态,默认false
}

2. 本地存储工具模块
创建localStorage.ts封装本地存储操作,通过泛型与类型断言确保数据读写的类型安全:

typescript
复制代码
// 保存任务到localStorage
export function saveTodos(todos: Todo[]): void {
  localStorage.setItem('todos', JSON.stringify(todos));
}

// 从localStorage读取任务,指定返回类型为Todo[]
export function getTodos(): Todo[] {
  const todosStr = localStorage.getItem('todos');
  // JSON.parse时通过类型断言指定返回类型,避免any类型
  return todosStr ? (JSON.parse(todosStr) as Todo[]) : [];
}

类型安全要点:使用as Todo[]断言JSON解析结果类型,防止因本地存储数据异常导致的类型错误;读取时默认返回空数组,确保函数返回值始终符合Todo[]类型。

3. DOM操作模块
创建dom.ts处理页面交互,重点解决DOM元素获取的类型断言问题:

typescript
复制代码
// 获取输入框元素,通过类型断言指定为HTMLInputElement
export const getInputElement = (): HTMLInputElement => {
  const element = document.getElementById('input');
  if (!element) throw new Error('Input element not found');
  // 明确断言元素类型,确保后续可访问value等InputElement特有属性
  return element as HTMLInputElement;
};

// 渲染任务列表,接收Todo数组并动态生成DOM
export function renderTodos(todos: Todo[]): void {
  const listElement = document.getElementById('todoList') as HTMLUListElement;
  listElement.innerHTML = todos.map(todo => `
    <li class="${todo.completed ? 'completed' : ''}">
      <input type="checkbox" checked="${todo.completed}" data-id="${todo.id}">
      <span>${todo.text}</span>
      <button data-id="${todo.id}">删除</button>
    </li>
  `).join('');
}

// 绑定事件处理函数,使用事件委托优化性能
export function bindEvents(onToggle: (id: string) => void, onDelete: (id: string) => void): void {
  const listElement = document.getElementById('todoList') as HTMLUListElement;
  listElement.addEventListener('change', (e) => {
    const target = e.target as HTMLInputElement;
    if (target.type === 'checkbox') {
      onToggle(target.dataset.id!); // 使用非空断言,确保data-id存在
    }
  });
  // 绑定删除按钮点击事件(省略类似实现)
}

4. 主逻辑整合
app.ts中组合各模块,实现完整业务流程:

typescript
复制代码
import { saveTodos, getTodos } from './localStorage';
import { getInputElement, renderTodos, bindEvents } from './dom';

// 初始化任务列表
let todos: Todo[] = getTodos();
renderTodos(todos);

// 绑定事件处理函数
bindEvents(
  (id) => { // 切换任务完成状态
    todos = todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo);
    saveTodos(todos);
    renderTodos(todos);
  },
  (id) => { // 删除任务
    todos = todos.filter(todo => todo.id !== id);
    saveTodos(todos);
    renderTodos(todos);
  }
);

// 处理添加任务逻辑
getInputElement().addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    const input = getInputElement();
    const text = input.value.trim();
    // 空任务校验:过滤空字符串或纯空格任务
    if (!text) return;
    
    // 添加新任务
    const newTodo: Todo = {
      id: Date.now().toString(),
      text,
      completed: false
    };
    todos.push(newTodo);
    saveTodos(todos);
    renderTodos(todos);
    input.value = ''; // 清空输入框
  }
});

运行与优化

确保应用稳定运行并提升用户体验:

  • 编译与运行:执行tsc命令触发TypeScript编译,编译器会将.ts文件转换为.js文件并输出至dist目录,直接在浏览器中打开HTML文件即可运行应用。
  • 关键优化点
    • 输入防抖:为输入框添加防抖处理(如使用setTimeoutclearTimeout),避免频繁输入时的性能损耗。
    • 空任务校验:在添加任务前通过trim()过滤空字符串,防止创建无意义任务。
    • 类型错误捕获:利用TypeScript编译时检查,提前发现DOM元素不存在、数据类型不匹配等问题。

调试技巧:编译时添加--watch参数(tsc --watch),实现代码修改后的自动重新编译,提升开发效率。

扩展练习

通过扩展功能深化TypeScript类型应用:

  • 任务分类功能:为Todo接口添加category字段,使用联合类型限制分类取值范围:
    typescript
    复制代码
    type Category = 'work' | 'life' | 'study'; // 联合类型定义分类选项
    
    interface TodoWithCategory extends Todo {
      category: Category; // 扩展原Todo接口,添加分类字段
    }
  • 实现路径:在UI中添加分类选择下拉框,存储时包含category字段,渲染时按分类分组展示,进一步练习类型扩展与联合类型应用。

通过本实战,开发者可系统掌握TypeScript在前端开发中的核心应用场景,包括类型定义、DOM类型处理、本地存储类型约束等,为构建更复杂的TypeScript应用奠定基础。