Skip to content

TypeScript 基础回顾

1. 基本类型

typescript
// 基础类型
const name: string = "Alice";
const age: number = 25;
const isStudent: boolean = true;
const anything: any = "can be anything";
const nothing: null = null;
const notDefined: undefined = undefined;

// 数组类型
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ["a", "b", "c"];
const mixed: (string | number)[] = [1, "two", 3];

// 元组
const tuple: [string, number] = ["hello", 42];

// 联合类型
type Status = "pending" | "success" | "error";
const status: Status = "pending";

// 字面量类型
type Direction = "up" | "down" | "left" | "right";

2. 接口与类型

typescript
// 接口 (interface)
interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // 可选属性
  readonly createdAt: Date; // 只读属性
}

// 类型别名 (type)
type Product = {
  id: number;
  name: string;
  price: number;
};

// 接口继承
interface AdminUser extends User {
  role: "admin" | "user";
  permissions: string[];
}

// 区别:interface 可以声明合并,type 不行
interface User {
  phone?: string; // 可以再次声明 User
}

3. 函数类型

typescript
// 函数类型注解
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b;

// 可选参数和默认参数
function greet(name: string, greeting: string = "Hello"): void {
  console.log(`${greeting}, ${name}`);
}

// 函数类型定义
type Callback = (data: string) => void;
const handleCallback: Callback = (data) => console.log(data);

// 重载
function process(input: string): string;
function process(input: number): number;
function process(input: string | number): string | number {
  if (typeof input === "string") {
    return input.toUpperCase();
  }
  return input * 2;
}

4. 泛型

typescript
// 基础泛型
function getFirst<T>(arr: T[]): T {
  return arr[0];
}

// 使用泛型
const firstNum = getFirst([1, 2, 3]); // type: number
const firstStr = getFirst(["a", "b"]); // type: string

// 泛型约束
interface HasId {
  id: number;
}

function printId<T extends HasId>(obj: T): void {
  console.log(obj.id);
}

// 多个泛型
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

// 泛型类
class Container<T> {
  private data: T;

  constructor(value: T) {
    this.data = value;
  }

  getValue(): T {
    return this.data;
  }
}

TypeScript + React 组件类型

5. 函数组件类型定义

5.1 基础函数组件

typescript
import { FC, ReactNode } from 'react';

// 方式1:使用 FC (FunctionComponent) 类型
const Welcome: FC<{ name: string }> = ({ name }) => {
  return <div>Welcome, {name}</div>;
};

// 方式2:定义 Props 接口后使用
interface ButtonProps {
  label: string;
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  disabled?: boolean;
  className?: string;
}

const Button: FC<ButtonProps> = ({ label, onClick, disabled, className }) => {
  return (
    <button onClick={onClick} disabled={disabled} className={className}>
      {label}
    </button>
  );
};

// 使用组件
<Button label="Click me" onClick={() => console.log('clicked')} />

5.2 带子元素的组件

typescript
import { FC, ReactNode } from 'react';

interface CardProps {
  title: string;
  children: ReactNode;  // 子元素
}

const Card: FC<CardProps> = ({ title, children }) => {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">{children}</div>
    </div>
  );
};

// 使用
<Card title="My Card">
  <p>This is content</p>
</Card>

5.3 事件处理类型

typescript
interface InputProps {
  value: string;
  onChange: (value: string) => void;
  onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
}

const Input: FC<InputProps> = ({ value, onChange, onBlur }) => {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      onBlur={onBlur}
    />
  );
};

// 常见事件类型
type ClickEvent = React.MouseEvent<HTMLButtonElement>;
type ChangeEvent = React.ChangeEvent<HTMLInputElement>;
type FormEvent = React.FormEvent<HTMLFormElement>;
type KeyboardEvent = React.KeyboardEvent<HTMLInputElement>;

6. Hooks 类型

6.1 useState 类型

typescript
import { useState } from "react";

// 方式1:类型自动推断
const [count, setCount] = useState(0); // type: number

// 方式2:显式指定类型
const [name, setName] = useState<string>("");
const [user, setUser] = useState<User | null>(null);

// 方式3:复杂对象
interface AppState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
}

const [state, setState] = useState<AppState>({
  user: null,
  isLoading: false,
  error: null,
});

// 更新状态
setState((prev) => ({ ...prev, isLoading: true }));

6.2 useEffect 类型

typescript
import { useEffect } from "react";

// 基础 useEffect
useEffect(() => {
  const timer = setTimeout(() => {
    console.log("done");
  }, 1000);

  return () => clearTimeout(timer); // 清理函数
}, []); // 依赖数组

// 异步操作
useEffect(() => {
  let isMounted = true;

  const fetchData = async () => {
    try {
      const response = await fetch("/api/users");
      const data: User[] = await response.json();
      if (isMounted) {
        setUsers(data);
      }
    } catch (error) {
      if (isMounted) {
        setError(error as Error);
      }
    }
  };

  fetchData();

  return () => {
    isMounted = false;
  };
}, []);

6.3 useReducer 类型

typescript
import { useReducer } from 'react';

// 定义 State 和 Action 类型
interface CounterState {
  count: number;
  history: number[];
}

type CounterAction =
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'RESET' }
  | { type: 'SET_VALUE'; payload: number };

const initialState: CounterState = {
  count: 0,
  history: []
};

function counterReducer(state: CounterState, action: CounterAction): CounterState {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
        history: [...state.history, state.count + 1]
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
        history: [...state.history, state.count - 1]
      };
    case 'SET_VALUE':
      return { ...state, count: action.payload };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'SET_VALUE', payload: 10 })}>Set 10</button>
    </div>
  );
}

6.4 useContext 类型

typescript
import { createContext, useContext, FC, ReactNode } from 'react';

// 定义 Context 类型
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

// Provider 组件
interface ThemeProviderProps {
  children: ReactNode;
}

const ThemeProvider: FC<ThemeProviderProps> = ({ children }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const value: ThemeContextType = {
    theme,
    toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

// 自定义 Hook(推荐使用方式)
function useTheme(): ThemeContextType {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// 使用
function Header() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>Current: {theme}</button>;
}

6.5 useRef 类型

typescript
import { useRef } from 'react';

// 引用 DOM 元素
const inputRef = useRef<HTMLInputElement>(null);

const focusInput = () => {
  inputRef.current?.focus();
};

// 引用非 DOM 值
const countRef = useRef<number>(0);

const increment = () => {
  countRef.current++;
  console.log(countRef.current);
};

// 使用
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>

7. 自定义 Hooks 类型

7.1 useForm Hook

typescript
interface FormState<T> {
  values: T;
  errors: Partial<Record<keyof T, string>>;
  touched: Partial<Record<keyof T, boolean>>;
}

interface UseFormOptions<T> {
  initialValues: T;
  onSubmit: (values: T) => void | Promise<void>;
  validate?: (values: T) => Partial<Record<keyof T, string>>;
}

function useForm<T extends Record<string, any>>({
  initialValues,
  onSubmit,
  validate
}: UseFormOptions<T>) {
  const [state, setState] = useState<FormState<T>>({
    values: initialValues,
    errors: {},
    touched: {}
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setState(prev => ({
      ...prev,
      values: { ...prev.values, [name]: value }
    }));
  };

  const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
    const { name } = e.target;
    setState(prev => ({
      ...prev,
      touched: { ...prev.touched, [name]: true }
    }));
  };

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    if (validate) {
      const errors = validate(state.values);
      setState(prev => ({ ...prev, errors }));

      if (Object.keys(errors).length > 0) {
        return;
      }
    }

    await onSubmit(state.values);
  };

  return {
    ...state,
    handleChange,
    handleBlur,
    handleSubmit,
    reset: () => setState({ values: initialValues, errors: {}, touched: {} })
  };
}

// 使用
interface LoginForm {
  email: string;
  password: string;
}

function LoginPage() {
  const form = useForm<LoginForm>({
    initialValues: { email: '', password: '' },
    validate: (values) => {
      const errors: Partial<Record<keyof LoginForm, string>> = {};
      if (!values.email) errors.email = 'Email required';
      if (!values.password) errors.password = 'Password required';
      return errors;
    },
    onSubmit: async (values) => {
      console.log('Submit:', values);
    }
  });

  return (
    <form onSubmit={form.handleSubmit}>
      <input
        name="email"
        value={form.values.email}
        onChange={form.handleChange}
      />
      {form.errors.email && <span>{form.errors.email}</span>}
    </form>
  );
}

7.2 useFetch Hook

typescript
interface UseFetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  headers?: Record<string, string>;
  body?: any;
}

interface UseFetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

function useFetch<T = any>(
  url: string,
  options?: UseFetchOptions
): UseFetchState<T> & { refetch: () => Promise<void> } {
  const [state, setState] = useState<UseFetchState<T>>({
    data: null,
    loading: true,
    error: null
  });

  const fetchData = useCallback(async () => {
    try {
      setState(prev => ({ ...prev, loading: true }));

      const response = await fetch(url, {
        method: options?.method || 'GET',
        headers: options?.headers,
        body: options?.body ? JSON.stringify(options.body) : undefined
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = (await response.json()) as T;
      setState({ data, loading: false, error: null });
    } catch (error) {
      setState({
        data: null,
        loading: false,
        error: error instanceof Error ? error : new Error(String(error))
      });
    }
  }, [url, options]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { ...state, refetch: fetchData };
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

function UserList() {
  const { data: users, loading, error, refetch } = useFetch<User[]>('/api/users');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <ul>
        {users?.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}