TypeScript 类型安全与类型体操实战:从入门到精通

前言

TypeScript 已成为现代前端开发的标配,但在实际项目中,真正掌握类型系统的开发者却不多见。作为在多个企业级 Vue3/React 项目中深度实践 TypeScript 的工程师,我在 SaaS 平台、微前端架构、数据可视化系统等场景中,积累了丰富的类型设计经验。本文将从类型基础出发,深入探讨 TypeScript 的高级类型技巧,结合 Vue3 和 React 生态分享可落地的类型安全最佳实践。


一、TypeScript 类型系统核心概念

1.1 类型推断与类型注解

TypeScript 的类型推断能力可以大大减少我们的代码量:

复制代码
// 类型推断示例
let message = 'Hello';      // 推断为 string
let count = 0;               // 推断为 number
let isActive = true;         // 推断为 boolean
let numbers = [1, 2, 3];     // 推断为 number[]
let user = { name: '张三', age: 30 }; // 推断为 { name: string; age: number }
​
// 显式类型注解
let greeting: string = 'Hello';
let total: number = 100;
let items: string[] = ['a', 'b', 'c'];
​
// 函数类型
function add(a: number, b: number): number {
  return a + b;
}
​
// 箭头函数类型
const multiply = (a: number, b: number): number => a * b;
​
// 回调函数类型
function fetchData(callback: (data: string) => void) {
  callback('data loaded');
}

1.2 联合类型与交叉类型

复制代码
// 联合类型:多种可能性
type Status = 'pending' | 'success' | 'error';
​
interface SuccessResponse {
  status: 'success';
  data: User;
}
​
interface ErrorResponse {
  status: 'error';
  error: {
    code: number;
    message: string;
  };
}
​
type ApiResponse = SuccessResponse | ErrorResponse;
​
// 类型守卫
function handleResponse(response: ApiResponse) {
  if (response.status === 'success') {
    // TypeScript 知道这是 SuccessResponse
    console.log(response.data.name);
  } else {
    // TypeScript 知道这是 ErrorResponse
    console.log(response.error.message);
  }
}
​
// 交叉类型:组合多个类型
interface HasName {
  name: string;
}
​
interface HasAge {
  age: number;
}
​
interface HasEmail {
  email: string;
}
​
type Contact = HasName & HasAge & HasEmail;
​
const contact: Contact = {
  name: '张三',
  age: 30,
  email: 'zhang@example.com'
};

1.3 泛型基础

复制代码
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}
​
const num = identity(42);         // number
const str = identity('hello');   // string
​
// 泛型接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}
​
interface User {
  id: string;
  name: string;
}
​
type UserResponse = ApiResponse<User[]>;
​
// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
​
const user = { id: '1', name: '张三', age: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age');   // number
// getProperty(user, 'invalid'); // 编译错误

二、实用类型工具详解

2.1 内置工具类型

TypeScript 提供了一系列内置工具类型,我们可以灵活运用:

复制代码
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  password: string;
}
​
// Partial<T> - 将所有属性变为可选
type PartialUser = Partial<User>;
​
// Required<T> - 将所有属性变为必需
type RequiredUser = Required<PartialUser>;
​
// Pick<T, K> - 从 T 中选择部分属性
type UserPreview = Pick<User, 'id' | 'name'>;
​
// Omit<T, K> - 从 T 中排除部分属性
type UserWithoutPassword = Omit<User, 'password'>;
​
// Record<K, V> - 创建键值对类型
type UserRole = 'admin' | 'editor' | 'viewer';
type Permission = 'read' | 'write' | 'delete';
​
const rolePermissions: Record<UserRole, Permission[]> = {
  admin: ['read', 'write', 'delete'],
  editor: ['read', 'write'],
  viewer: ['read']
};
​
// Exclude<T, U> - 从 T 中排除可分配给 U 的类型
type Status = 'pending' | 'success' | 'error' | 'loading';
type NonErrorStatus = Exclude<Status, 'error'>; // 'pending' | 'success' | 'loading'
​
// Extract<T, U> - 从 T 中提取可分配给 U 的类型
type ErrorStatus = Extract<Status, 'error' | 'loading'>; // 'error' | 'loading'
​
// NonNullable<T> - 排除 null 和 undefined
type MaybeUser = User | null | undefined;
type DefinitelyUser = NonNullable<MaybeUser>; // User
​
// ReturnType<T> - 获取函数返回类型
function createUser() {
  return { id: '1', name: '张三' };
}
type CreatedUser = ReturnType<typeof createUser>; // { id: string; name: string }
​
// Parameters<T> - 获取函数参数类型
function updateUser(id: string, data: Partial<User>) {}
type UpdateUserParams = Parameters<typeof updateUser>; // [id: string, data: Partial<User>]

2.2 高级类型技巧

复制代码
// 条件类型
type IsArray<T> = T extends any[] ? true : false;
​
type A = IsArray<string[]>; // true
type B = IsArray<string>;   // false
​
// 条件类型 + infer
type ArrayElement<T> = T extends (infer E)[] ? E : never;
​
type Element = ArrayElement<string[]>; // string
type Nested = ArrayElement<number[][]>; // number[]
​
// 获取 Promise 返回值类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
​
type PromiseResult = Awaited<Promise<User>>; // User
​
// 递归类型 - 深只读
type DeepReadonly<T> = T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;
​
type ReadonlyUser = DeepReadonly<User>;
// {
//   readonly id: string;
//   readonly name: string;
//   readonly email: string;
//   readonly age: number;
//   readonly password: string;
// }
​
// 深度 Partial
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;
​
// 深度 Required
type DeepRequired<T> = T extends object
  ? { [K in keyof T]-?: DeepRequired<T[K]> }
  : T;
​
// 任意深度键路径
type DeepKeys<T, P extends string = ''> = T extends object
  ? {
      [K in keyof T & string]: T[K] extends object
        ? DeepKeys<T[K], `${P}${K}`> | `${P}${K}`
        : `${P}${K}`
    }[keyof T & string]
  : never;
​
type UserKeys = DeepKeys<User>; // 'id' | 'name' | 'email' | 'age' | 'password'
type NestedKeys = DeepKeys<{ a: { b: { c: string } } }>; // 'a' | 'a.b' | 'a.b.c'

三、Vue3 组合式 API 类型实践

3.1 响应式数据类型定义

复制代码
import { ref, reactive, computed, Ref } from 'vue';
​
// ref 类型推断
const count = ref(0);           // Ref<number>
const name = ref('张三');        // Ref<string>
const user = ref({ name: '张三' }); // Ref<{ name: string }>
​
// 显式指定泛型
const countExplicit = ref<number>(0);
const data = ref<User | null>(null);
​
// reactive 类型
const state = reactive({
  count: 0,
  user: { name: '张三', age: 30 },
  items: ['a', 'b', 'c']
}); // { count: number; user: { name: string; age: number }; items: string[] }
​
// 显式类型定义
interface FormState {
  username: string;
  password: string;
  remember: boolean;
}
​
const form = reactive<FormState>({
  username: '',
  password: '',
  remember: false
});
​
// computed 类型(自动推断)
const doubled = computed(() => count.value * 2); // ComputedRef<number>
const fullName = computed(() => `${user.value.name} - ${user.value.age}`); // ComputedRef<string>
​
// 带 get/set 的 computed
const message = computed({
  get: () => `Hello, ${name.value}`,
  set: (val: string) => { name.value = val.replace('Hello, ', ''); }
});

3.2 Props 与 Emits 类型定义

复制代码
import { defineProps, defineEmits, PropType } from 'vue';
​
// Props 类型定义
interface User {
  id: string;
  name: string;
  avatar?: string;
  role: 'admin' | 'user' | 'guest';
}
​
interface Props {
  // 基础类型
  title: string;
  count: number;
  // 可选属性
  description?: string;
  // 联合类型
  status: 'loading' | 'success' | 'error';
  // 复杂类型
  user: User;
  // 数组类型
  tags: string[];
  // 函数类型
  onClick: () => void;
  onChange: (value: string) => void;
}
​
const props = defineProps<Props>();
​
// 带默认值的 Props
interface WithDefaults {
  title: string;
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}
​
const propsWithDefaults = withDefaults(defineProps<WithDefaults>(), {
  size: 'medium',
  disabled: false
});
​
// Emits 类型定义
interface Emits {
  (e: 'update', value: string): void;
  (e: 'delete', id: string): void;
  (e: 'click', event: MouseEvent): void;
}
​
const emit = defineEmits<Emits>();
​
// 使用
emit('update', 'new value');
emit('delete', '123');
emit('click', new MouseEvent('click'));

3.3 组合式函数(Composables)类型设计

复制代码
import { ref, computed, Ref, ComputedRef } from 'vue';
​
// 泛型组合式函数
export function useLocalStorage<T>(key: string, defaultValue: T) {
  const value = ref<T>(defaultValue);
  
  // 从 localStorage 读取
  const stored = localStorage.getItem(key);
  if (stored) {
    try {
      value.value = JSON.parse(stored);
    } catch {
      console.error(`Failed to parse localStorage key: ${key}`);
    }
  }
  
  // 监听变化并保存
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue));
  }, { deep: true });
  
  return value;
}
​
// 使用示例
const theme = useLocalStorage('theme', 'light'); // Ref<string>
const userPrefs = useLocalStorage('userPrefs', { sidebar: true }); // Ref<{ sidebar: boolean }>
​
// 带类型约束的组合式函数
interface PaginationOptions {
  page: number;
  pageSize: number;
  total: number;
}
​
interface UsePaginationReturn<T> {
  data: Ref<T[]>;
  loading: Ref<boolean>;
  pagination: ComputedRef<PaginationOptions>;
  totalPages: ComputedRef<number>;
  hasNextPage: ComputedRef<boolean>;
  hasPrevPage: ComputedRef<boolean>;
  nextPage: () => void;
  prevPage: () => void;
  goToPage: (page: number) => void;
}
​
export function usePagination<T>(
  fetchFn: (page: number, pageSize: number) => Promise<T[]>
): UsePaginationReturn<T> {
  const data = ref<T[]>([]) as Ref<T[]>;
  const loading = ref(false);
  const currentPage = ref(1);
  const pageSize = ref(10);
  const total = ref(0);
  
  const pagination = computed(() => ({
    page: currentPage.value,
    pageSize: pageSize.value,
    total: total.value
  }));
  
  const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
  const hasNextPage = computed(() => currentPage.value < totalPages.value);
  const hasPrevPage = computed(() => currentPage.value > 1);
  
  async function fetchData() {
    loading.value = true;
    try {
      const result = await fetchFn(currentPage.value, pageSize.value);
      data.value = result;
    } finally {
      loading.value = false;
    }
  }
  
  function nextPage() {
    if (hasNextPage.value) {
      currentPage.value++;
      fetchData();
    }
  }
  
  function prevPage() {
    if (hasPrevPage.value) {
      currentPage.value--;
      fetchData();
    }
  }
  
  function goToPage(page: number) {
    currentPage.value = Math.max(1, Math.min(page, totalPages.value));
    fetchData();
  }
  
  return {
    data,
    loading,
    pagination,
    totalPages,
    hasNextPage,
    hasPrevPage,
    nextPage,
    prevPage,
    goToPage
  };
}

3.4 Vue Router 类型安全

复制代码
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';

// 路由元信息类型定义
interface Meta {
  title: string;
  requiresAuth?: boolean;
  roles?: ('admin' | 'user')[];
  keepAlive?: boolean;
}

// 全局类型声明
declare module 'vue-router' {
  interface RouteMeta extends Meta {}
}

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('@/layouts/MainLayout.vue'),
    meta: { title: '首页', requiresAuth: false },
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: () => import('@/views/Dashboard.vue'),
        meta: { title: '仪表盘' }
      },
      {
        path: 'users/:id',
        name: 'UserDetail',
        component: () => import('@/views/UserDetail.vue'),
        meta: { title: '用户详情', requiresAuth: true, roles: ['admin'] },
        props: true // route.params 作为 props
      }
    ]
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

// 路由守卫类型安全
router.beforeEach((to, from, next) => {
  // 类型安全的 meta 访问
  const { requiresAuth, roles, title } = to.meta;
  
  if (requiresAuth && !isAuthenticated()) {
    next({ name: 'Login', query: { redirect: to.fullPath } });
    return;
  }
  
  if (roles && !hasRole(roles)) {
    next({ name: 'Forbidden' });
    return;
  }
  
  document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`;
  next();
});

// 类型安全的路由跳转
function navigateToUser(id: string) {
  router.push({
    name: 'UserDetail',
    params: { id }, // number 会自动转为 string
    query: { tab: 'profile' },
    hash: '#settings'
  });
}

四、React 类型最佳实践

4.1 组件 Props 类型设计

复制代码
import React, { useState, useCallback, ReactNode, MouseEvent, ChangeEvent } from 'react';

// 基础 Props
interface ButtonProps {
  children: ReactNode;
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

function Button({
  children,
  onClick,
  variant = 'primary',
  size = 'medium',
  disabled = false
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

// 泛型组件
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => ReactNode;
  keyExtractor: (item: T) => string;
  emptyMessage?: string;
}

function List<T>({ items, renderItem, keyExtractor, emptyMessage = 'No items' }: ListProps<T>) {
  if (items.length === 0) {
    return <div className="empty">{emptyMessage}</div>;
  }
  
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

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

const UserList: React.FC<{ users: User[] }> = ({ users }) => (
  <List
    items={users}
    keyExtractor={(user) => user.id}
    renderItem={(user) => (
      <div>
        <strong>{user.name}</strong>
        <span>{user.email}</span>
      </div>
    )}
  />
);

// 受控组件 Props
interface InputProps {
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
  type?: 'text' | 'email' | 'password' | 'number';
  error?: string;
  disabled?: boolean;
}

function Input({
  value,
  onChange,
  placeholder,
  type = 'text',
  error,
  disabled
}: InputProps) {
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    onChange(e.target.value);
  };
  
  return (
    <div className={`input-wrapper ${error ? 'has-error' : ''}`}>
      <input
        type={type}
        value={value}
        onChange={handleChange}
        placeholder={placeholder}
        disabled={disabled}
      />
      {error && <span className="error-message">{error}</span>}
    </div>
  );
}

4.2 Context 类型安全

复制代码
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';

// 1. 定义 Context 类型
interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
}

interface AuthContextValue extends AuthState {
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  updateUser: (data: Partial<User>) => void;
}

// 2. 创建 Context(带默认值)
const AuthContext = createContext<AuthContextValue | null>(null);

// 3. Provider 组件
interface AuthProviderProps {
  children: ReactNode;
}

export function AuthProvider({ children }: AuthProviderProps) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  
  const isAuthenticated = user !== null;
  
  const login = useCallback(async (email: string, password: string) => {
    setIsLoading(true);
    try {
      const response = await api.login({ email, password });
      setUser(response.user);
    } finally {
      setIsLoading(false);
    }
  }, []);
  
  const logout = useCallback(() => {
    setUser(null);
    api.logout();
  }, []);
  
  const updateUser = useCallback((data: Partial<User>) => {
    setUser(prev => prev ? { ...prev, ...data } : null);
  }, []);
  
  const value: AuthContextValue = {
    user,
    isAuthenticated,
    isLoading,
    login,
    logout,
    updateUser
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// 4. 自定义 Hook(带类型安全的访问)
export function useAuth(): AuthContextValue {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  
  return context;
}

// 使用示例
function ProfileButton() {
  const { user, isAuthenticated, logout } = useAuth();
  
  if (!isAuthenticated) {
    return <Link to="/login">登录</Link>;
  }
  
  return (
    <div>
      <span>欢迎, {user!.name}</span>
      <button onClick={logout}>退出</button>
    </div>
  );
}

4.3 Hooks 类型定义

复制代码
import { useState, useEffect, useRef, useCallback, DependencyList } from 'react';

// useDebounce
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);
  
  return debouncedValue;
}

// useAsync
interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

interface UseAsyncReturn<T> extends AsyncState<T> {
  execute: () => Promise<void>;
  reset: () => void;
}

export function useAsync<T>(
  asyncFn: () => Promise<T>,
  dependencies: DependencyList = []
): UseAsyncReturn<T> {
  const [state, setState] = useState<AsyncState<T>>({
    data: null,
    loading: true,
    error: null
  });
  
  useEffect(() => {
    let mounted = true;
    
    async function execute() {
      setState({ data: null, loading: true, error: null });
      
      try {
        const result = await asyncFn();
        if (mounted) {
          setState({ data: result, loading: false, error: null });
        }
      } catch (error) {
        if (mounted) {
          setState({ data: null, loading: false, error: error as Error });
        }
      }
    }
    
    execute();
    
    return () => {
      mounted = false;
    };
  }, dependencies);
  
  const reset = useCallback(() => {
    setState({ data: null, loading: false, error: null });
  }, []);
  
  return { ...state, execute: asyncFn, reset };
}

// useClickOutside
export function useClickOutside<T extends HTMLElement>(
  callback: () => void
): React.RefObject<T | null> {
  const ref = useRef<T>(null);
  
  useEffect(() => {
    function handleClickOutside(event: MouseEvent) {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        callback();
      }
    }
    
    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [callback]);
  
  return ref;
}

五、工程实践:类型安全的 API 层

5.1 统一 API 响应类型

复制代码
// types/api.ts

// API 响应基础类型
interface BaseResponse<T = unknown> {
  code: number;
  message: string;
}

interface SuccessResponse<T> extends BaseResponse {
  code: 0 | 200;
  data: T;
}

interface ErrorResponse extends BaseResponse {
  code: number;
  error?: {
    code: string;
    details?: Record<string, string[]>;
  };
}

type ApiResult<T> = SuccessResponse<T> | ErrorResponse;

// 提取 data 类型
type ApiData<T> = T extends SuccessResponse<infer D> ? D : never;

// 分页类型
interface PaginationParams {
  page: number;
  pageSize: number;
}

interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
}

// 请求配置
interface RequestConfig {
  baseURL: string;
  timeout: number;
  headers?: Record<string, string>;
}

5.2 Axios 封装与类型推断

复制代码
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiResult, SuccessResponse } from './types/api';

// 创建 axios 实例
const api: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // 处理未授权
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// 类型安全的请求方法
async function request<T>(
  config: AxiosRequestConfig
): Promise<SuccessResponse<T>> {
  const response: AxiosResponse<ApiResult<T>> = await api(config);
  
  if (response.data.code !== 0 && response.data.code !== 200) {
    throw new Error(response.data.message);
  }
  
  return response.data as SuccessResponse<T>;
}

// API 方法类型
type ApiMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

function createApiMethod<M extends ApiMethod>(method: M) {
  return async <T, D = unknown>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig
  ): Promise<SuccessResponse<T>> => {
    return request<T>({
      method,
      url,
      ...(method === 'get' ? { params: data } : { data }),
      ...config
    });
  };
}

// API 方法
export const get = createApiMethod('get');
export const post = createApiMethod('post');
export const put = createApiMethod('put');
export const patch = createApiMethod('patch');
export const del = createApiMethod('delete');

5.3 API 模块化组织

复制代码
// api/user.ts
import { get, post } from './client';
import type { User, UserCreate, UserUpdate } from '@/types/models';

export const userApi = {
  list: (params: { page: number; pageSize: number }) =>
    get<PaginatedResponse<User>>('/users', params),
  
  getById: (id: string) =>
    get<User>(`/users/${id}`),
  
  create: (data: UserCreate) =>
    post<User>('/users', data),
  
  update: (id: string, data: UserUpdate) =>
    post<User>(`/users/${id}`, data),
  
  delete: (id: string) =>
    post<void>(`/users/${id}/delete`, {}),
  
  updatePassword: (id: string, password: string) =>
    post<void>(`/users/${id}/password`, { password })
};

// api/dashboard.ts
import { get } from './client';
import type { DashboardStats } from '@/types/models';

export const dashboardApi = {
  getStats: () =>
    get<DashboardStats>('/dashboard/stats'),
  
  getTrend: (params: { period: 'day' | 'week' | 'month' }) =>
    get<TrendData[]>('/dashboard/trend', params),
  
  getRankings: () =>
    get<RankingItem[]>('/dashboard/rankings')
};

// 使用示例
async function fetchUserList() {
  try {
    const response = await userApi.list({ page: 1, pageSize: 20 });
    // response.data 自动类型化为 PaginatedResponse<User>
    console.log(response.data.items);
    console.log(response.data.total);
  } catch (error) {
    console.error('Failed to fetch users:', error);
  }
}

六、类型测试与类型守卫

6.1 类型守卫函数

复制代码
// 基础类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isNumber(value: unknown): value is number {
  return typeof value === 'number';
}

function isArray(value: unknown): value is unknown[] {
  return Array.isArray(value);
}

function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

// 自定义类型守卫
interface Dog {
  kind: 'dog';
  bark(): void;
}

interface Cat {
  kind: 'cat';
  meow(): void;
}

type Animal = Dog | Cat;

function isDog(animal: Animal): animal is Dog {
  return animal.kind === 'dog';
}

function makeSound(animal: Animal) {
  if (isDog(animal)) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// 复杂的类型守卫
interface ApiErrorResponse {
  status: 'error';
  error: {
    code: string;
    message: string;
  };
}

interface ApiSuccessResponse<T> {
  status: 'success';
  data: T;
}

type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;

function isSuccessResponse<T>(
  response: ApiResponse<T>
): response is ApiSuccessResponse<T> {
  return response.status === 'success';
}

function handleResponse<T>(response: ApiResponse<T>) {
  if (isSuccessResponse(response)) {
    // TypeScript 知道这是 ApiSuccessResponse<T>
    console.log(response.data);
  } else {
    // TypeScript 知道这是 ApiErrorResponse
    console.error(response.error.message);
  }
}

6.2 穷举检查

复制代码
// never 类型用于穷举检查
type Status = 'pending' | 'success' | 'error';

function handleStatus(status: Status) {
  switch (status) {
    case 'pending':
      return '处理中...';
    case 'success':
      return '成功';
    case 'error':
      return '失败';
    default:
      // never 确保所有情况都被处理
      const _exhaustive: never = status;
      throw new Error(`Unknown status: ${_exhaustive}`);
  }
}

// 联合类型穷举检查
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';

const buttonStyles: Record<ButtonVariant, string> = {
  primary: 'btn-primary',
  secondary: 'btn-secondary',
  danger: 'btn-danger',
  ghost: 'btn-ghost'
};

function getButtonClass(variant: ButtonVariant): string {
  // 如果新增 variant 但忘记更新 buttonStyles,
  // TypeScript 会报错
  return buttonStyles[variant];
}

七、总结

TypeScript 的类型系统是一把双刃剑:使用得当可以让代码坚不可摧,使用不当反而会增加复杂度。通过本文的实践分享,我们可以得出以下关键结论:

类型设计原则:

  1. 类型优先 --- 优先设计类型,再实现逻辑

  2. 最小类型 --- 使用最具体的类型,不要过度泛化

  3. 类型即文档 --- 好的类型定义胜过千言万语

  4. DRY 原则 --- 善用工具类型,避免重复定义

Vue3 类型实践:

  1. ref<T>() / reactive<T>() 泛型指定复杂类型

  2. defineProps<T>() / defineEmits<T>() 强类型 Props 和事件

  3. 组合式函数返回精确的 Ref / ComputedRef 类型

  4. Router 的 RouteMeta 扩展实现类型安全的 meta

React 类型实践:

  1. Props 接口清晰定义,泛型组件复用逻辑

  2. Context 提供完整的类型约束

  3. 自定义 Hook 返回值类型精确化

  4. API 层类型自动推断,减少手动类型标注

工程化建议:

  1. 开启 strict 模式,逐步解决类型错误

  2. 使用 tsconfig.jsonstrict 系列选项

  3. 配置 ESLint 的 @typescript-eslint 规则

  4. 编写类型测试,确保关键类型的正确性

在实践中,TypeScript 的类型系统是一座可以无限探索的宝库。掌握这些技巧,不仅能提升代码质量,更能让我们在重构和扩展时游刃有余。


参考资源:

💡 提示:类型系统是渐进式的,建议从简单类型开始,逐步引入高级技巧,避免过度设计。

相关推荐
不会写DN2 小时前
Vue3中的computed 与 watch 的区别
javascript·面试·vue
柳杉2 小时前
HTML-in-Canvas:让 Canvas 完美渲染 HTML 的 Web 新标准
前端·javascript
cTz6FE7gA2 小时前
WebGL实战:用Three.js创建3D场景,实现沉浸式Web体验
前端·javascript·webgl
We་ct3 小时前
LeetCode 69. x 的平方根:两种解法详解
前端·javascript·算法·leetcode·typescript·平方
daad7773 小时前
WSL2_wifi驱动安装
开发语言·前端·javascript
CV-杨帆3 小时前
AAAI 2026 大模型安全相关论文整理
安全
2501_948114243 小时前
Claude Sonnet 4.6 深度评测:性能逼近 Opus、成本打骨折,附接入方案与选型指南
大数据·网络·人工智能·安全·架构
humors2213 小时前
一些安全类网站(不定期更新)
linux·网络·windows·安全·黑客·白帽
喵叔哟3 小时前
6.【.NET10 实战--孢子记账--产品智能化】--认证与安全包
python·安全·flask