JS的超集——TypeScript

JS的超集------TypeScript

在编程的世界里,有一种Bug总是在你最不期待的时候跳出来,让你抓耳挠腮,怀疑人生。它不是编译时的错误,也不是运行时的异常,而是那种"我明明写对了,为什么就是不工作"的诡异现象。这种现象在JavaScript中尤为常见,因为它是弱类型、动态语言,编译时不会报错,只有运行时才会暴露问题。

从Bug说起

让我们先看看一个简单的JavaScript代码片段:

javascript 复制代码
function addTodo(title) {
  const newTodo = {
    id: Date.now(),
    title: title,
    completed: false
  };
  setTodos([...todos, newTodo]);
}

这段代码看起来很合理,对吧?但是,如果有人不小心把数字传给了addTodo,比如addTodo(123),会发生什么?在JavaScript中,这不会报错,但会导致title属性变成123,而不是期望的字符串。这种问题只有在运行时才会暴露出来,而这时可能已经造成了严重的后果。

更糟糕的是,当你在大型项目中工作时,这种Bug会像病毒一样蔓延,让你花费大量时间在调试上,而不是写新功能。想象一下,你花了一整天时间调试一个"为什么我的标题显示成数字"的问题,最后发现只是因为有人不小心把addTodo(123)当成了字符串,这种体验简直让人想哭。

TypeScript:为你的代码穿上"防弹衣"

TypeScript就是为了解决这类问题而生的。它不是一种新的语言,而是JavaScript的超集,添加了静态类型系统。这意味着:

  • 静态类型:在编写代码时就定义类型,而不是等到运行时
  • 边写边检查bug:IDE会在你写代码时就提示可能的错误
  • 编译时检查错误:在代码运行前就发现潜在问题
  • 代码建议、文档查看都非常方便:类型系统为IDE提供了强大的支持
  • 没有未使用的变量等垃圾代码提示:TypeScript会告诉你哪些代码是多余的

想象一下,如果你的代码有"防弹衣",在你写代码的时候就能告诉你"这里可能有问题",而不是等到用户投诉时才发现。这就是TypeScript带来的体验。

TypeScript的类型系统:不只是简单的"字符串"和"数字"

TypeScript的类型系统远比你想象的丰富。让我们来看看几个关键的类型:

TS 复制代码
// 泛型 类型的传参 T
let arr2:Array<string> = ['a','b','c'];

// 任意类型 可以放弃类型约束
let aa:any = 1; 
aa = '11';
aa = true;
aa = {};
aa.hello(); // 任意类型可以调用任何方法

// 未知类型 更安全一些  使用前做类型检测
let bb:unknown = 1; 
bb = '11';
bb = {};
// bb.hello();  会报错 未知类型可以接收任何类型,但是不可以调用任何方法

// 接口  约定对象具有哪些属性和方法
interface User{
    name:string;
    age:number;
    readonly id :number;
    hobby?:string;
}

const u:User = {
    id:1,
    name:'李四',
    age:18,
    hobby:'篮球',
}
u.name = '李四少爷'; // 可修改
// u.id = 11; // 只读属性 不能修改

// 自定义类型
type ID = string | number; 
let num:ID = '234'

type UserType = {
    name:string;
    age:number;
    hobby?:string;
}
  1. 泛型(Generics) :允许你创建可重用的组件,这些组件可以处理多种类型。例如,useState<Todo[]>,这里的<Todo[]>就是泛型参数,告诉TypeScript这个状态是Todo对象的数组。
  2. any vs unknownany是TypeScript中的"任何类型",它会关闭类型检查。unknown则是一个更安全的选择,表示"我们不知道这个类型是什么,但它是某种类型"。使用unknown需要在使用前进行类型检查。
  3. 接口(Interfaces) :用于定义对象的结构。例如,interface Todo { id: number; title: string; completed: boolean; }
  4. type :用于定义类型的别名,可以是基础类型、联合类型或交叉类型。例如,type ID = number;

用TypeScript重写Todo应用:从数据模型开始

在开始重写我们的Todo应用之前,我们先要定义数据模型。在TypeScript中,我们使用接口来定义数据结构:

typescript 复制代码
// 数据模型:Todo 接口
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

这个接口定义了Todo对象的结构。在每个需要使用Todo的地方,我们都可以引用这个接口,确保数据的结构正确。这就像给我们的数据加了一个"身份证",确保它始终符合我们的预期。

组件Props的类型约束

在React中,组件的Props是重要的部分。在TypeScript中,我们可以为Props定义明确的类型:

typescript 复制代码
// 组件Props类型
interface Props {
  todos: Todo[];
  onToggle: (id: number) => void;
  onRemove: (id: number) => void;
}

const TodoList = ({ todos, onToggle, onRemove }: Props) => {
  // 组件逻辑
};

这里,Props接口确保了todos是Todo对象的数组,onToggleonRemove是接收数字并返回void的函数。这避免了在组件中使用错误的类型。

注意 :在React中,React.FC是老版本的写法,现代React推荐使用直接函数声明的写法:

typescript 复制代码
const TodoList = ({ todos, onToggle, onRemove }: Props) => {
  // ...
}

这种写法更简洁,而且TypeScript会自动推断返回类型。

状态管理的类型安全

在React中,我们经常使用useState来管理状态。在TypeScript中,我们可以为状态指定类型:

typescript 复制代码
const [todos, setTodos] = useState<Todo[]>(() => getStorage<Todo[]>(STORAGE_KEY, []));

这里,<Todo[]>是泛型参数,告诉TypeScript这个状态是Todo对象的数组。这样,当我们使用setTodos时,TypeScript会确保我们传递的是正确的类型。

持久化功能:TypeScript的实现

最后,我们来看看如何使用TypeScript实现持久化功能:

typescript 复制代码
// 存储工具:处理持久化
export function getStorage<T>(key: string, defaultValue: T): T {
  const value = localStorage.getItem(key);
  return value ? JSON.parse(value) : defaultValue;
}

export function setStorage<T>(key: string, value: T): T {
  localStorage.setItem(key, JSON.stringify(value));
  return value;
}

这些函数使用了泛型,可以处理任何类型的数据。例如,getStorage<Todo[]>(STORAGE_KEY, [])会从localStorage中获取Todo数组,TypeScript会确保我们得到的是正确的类型。

完整代码:TypeScript版本的Todo应用

以下是使用TypeScript重写的Todo应用的完整代码。所有文件都使用了类型安全的写法,确保类型错误在编译时就被捕获。

本文只介绍该项目使用TS编写时的注意事项,不介绍项目思路

数据模型:Todo接口

typescript 复制代码
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

存储工具:处理持久化

typescript 复制代码
export function getStorage<T>(key: string, defaultValue: T): T {
  const value = localStorage.getItem(key);
  return value ? JSON.parse(value) : defaultValue;
}

export function setStorage<T>(key: string, value: T): T {
  localStorage.setItem(key, JSON.stringify(value));
  return value;
}

自定义Hook:useTodos

typescript 复制代码
import { useState, useEffect } from 'react';
import { Todo } from './types/todo';
import { getStorage, setStorage } from './utils/storage';

const STORAGE_KEY = 'todos';

export function useTodos() {
  const [todos, setTodos] = useState<Todo[]>(() => getStorage<Todo[]>(STORAGE_KEY, []));
  
  const addTodo = (title: string) => {
    const newTodo: Todo = {
      id: Date.now(),
      title,
      completed: false,
    };
    setTodos([...todos, newTodo]);
  };
  
  const toggleTodo = (id: number) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  const removeTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  useEffect(() => {
    setStorage(STORAGE_KEY, todos);
  }, [todos]);
  
  return { todos, addTodo, toggleTodo, removeTodo };
}

组件:TodoList

typescript 复制代码
import React from 'react';
import TodoItem from './TodoItem';

interface Props {
  todos: Todo[];
  onToggle: (id: number) => void;
  onRemove: (id: number) => void;
}

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

export default TodoList;

组件:TodoItem

typescript 复制代码
import React from 'react';
import { Todo } from './types/todo';

interface Props {
  todo: Todo;
  onToggle: (id: number) => void;
  onRemove: (id: number) => void;
}

const TodoItem = ({ todo, onToggle, onRemove }: Props) => {
  return (
    <li>
      <input 
        type="checkbox" 
        checked={todo.completed} 
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.title}
      </span>
      <button onClick={() => onRemove(todo.id)}>Delete</button>
    </li>
  );
};

export default TodoItem;

组件:TodoInput

typescript 复制代码
import React, { useState } from 'react';

interface Props {
  onAdd: (title: string) => void;
}

const TodoInput = ({ onAdd }: Props) => {
  const [title, setTitle] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (title.trim()) {
      onAdd(title);
      setTitle('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={title} 
        onChange={e => setTitle(e.target.value)} 
        placeholder="Add a new todo"
      />
      <button type="submit">Add</button>
    </form>
  );
};

export default TodoInput;

主应用:App

typescript 复制代码
import React from 'react';
import { useTodos } from './hooks/useTodos';
import TodoList from './components/TodoList';
import TodoInput from './components/TodoInput';

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

结构:src目录

项目结构如下:

总结

通过TypeScript,我们不仅避免了"神秘"Bug,还让代码更加清晰、可维护。在Todo应用中,我们使用TypeScript定义了数据模型、组件Props、状态和自定义Hook,确保了类型安全。

TypeScript不是一种负担,而是一种提升开发体验的工具。它让你在写代码时就能发现潜在的问题,而不是等到运行时。当你看到IDE在你写代码时就提示"这里可能有问题",你会爱上这种体验。

所以,下次当你想写一个React应用时,为什么不试试TypeScript呢?它可能会改变你对前端开发的看法。毕竟,谁不想在写代码时就避免那些Bug,让开发过程更加顺畅呢?

相关推荐
EndingCoder2 小时前
高级类型:联合类型和类型别名
linux·服务器·前端·ubuntu·typescript
yyyao2 小时前
🔥🔥🔥 React18 源码学习 - Render 阶段(构造 Fiber 树)
react.js·源码阅读
有意义2 小时前
TypeScript 不是加法,是减法
react.js·typescript·前端框架
Ulyanov2 小时前
高级可视化技术——让PyVista数据展示更专业
开发语言·前端·人工智能·python·tkinter·gui开发
开开心心_Every2 小时前
重复图片智能清理工具:快速查重批量删除
java·服务器·开发语言·前端·学习·edge·powerpoint
Coffeeee2 小时前
了解一下Android16更新事项,拿捏下一波适配
android·前端·google
亿元程序员2 小时前
拖尾特效怎么实现?Cocos : 开箱即用!
前端
Wpa.wk2 小时前
性能测试 - JMeter练习-JMeter录制Web端压测脚本操作步骤
java·前端·经验分享·jmeter·自动化
undefined在掘金390412 小时前
wpf 布局专题
前端