本实战项目旨在通过纯TypeScript实现一个具备本地存储功能的Todo应用,帮助开发者掌握TypeScript在前端基础开发中的核心应用,包括类型定义、DOM操作类型处理、本地存储数据类型约束等关键技能。
从功能与交互层面明确应用边界,确保开发目标清晰:
功能优先级:本地存储功能需确保数据可靠性,DOM操作需处理类型安全,任务状态更新需实时反馈至UI,形成"输入-存储-渲染"的完整闭环。
搭建基础开发环境与技术框架,为TypeScript开发提供支持:
tsconfig.json中设置关键编译选项,确保代码兼容性与输出控制:target: ES6:指定编译目标为ES6,支持现代JavaScript特性module: ES6:采用ES6模块系统,便于代码拆分与导入outDir: dist:将编译后的JavaScript文件统一输出至dist目录HTMLElement(通用元素类型)、HTMLInputElement(输入框元素类型)、HTMLButtonElement(按钮元素类型)等,为DOM操作提供类型约束。采用模块化思想拆分代码,实现高内聚低耦合的应用架构:
1. 核心类型定义
首先定义任务数据的基础结构,通过interface明确Todo对象的类型约束,确保数据格式一致性:
// 定义任务数据结构
interface Todo {
id: string; // 任务唯一标识,可通过Date.now().toString()生成
text: string; // 任务描述文本
completed: boolean; // 任务完成状态,默认false
}
2. 本地存储工具模块
创建localStorage.ts封装本地存储操作,通过泛型与类型断言确保数据读写的类型安全:
// 保存任务到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元素获取的类型断言问题:
// 获取输入框元素,通过类型断言指定为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中组合各模块,实现完整业务流程:
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文件即可运行应用。setTimeout与clearTimeout),避免频繁输入时的性能损耗。trim()过滤空字符串,防止创建无意义任务。调试技巧:编译时添加--watch参数(tsc --watch),实现代码修改后的自动重新编译,提升开发效率。
通过扩展功能深化TypeScript类型应用:
category字段,使用联合类型限制分类取值范围:
type Category = 'work' | 'life' | 'study'; // 联合类型定义分类选项
interface TodoWithCategory extends Todo {
category: Category; // 扩展原Todo接口,添加分类字段
}
category字段,渲染时按分类分组展示,进一步练习类型扩展与联合类型应用。通过本实战,开发者可系统掌握TypeScript在前端开发中的核心应用场景,包括类型定义、DOM类型处理、本地存储类型约束等,为构建更复杂的TypeScript应用奠定基础。