TypeScript 变量声明完全指南

32 阅读6分钟

变量声明是 TypeScript 编程的基础,但其中蕴含着丰富的技巧和最佳实践。本文将深入探讨 TypeScript 中各种变量声明方式,带你从入门到精通。

一、TypeScript 变量声明基础

1.1 变量声明的基本语法

TypeScript 提供了三种主要的变量声明方式:varletconst,结合类型注解实现类型安全。

// 1. 使用 var (不推荐在新代码中使用)
var userName: string = "John";

// 2. 使用 let (推荐用于可重新赋值的变量)
let age: number = 25;

// 3. 使用 const (推荐用于不可重新赋值的变量)
const pi: number = 3.14159;

// 类型注解在变量名后,使用冒号分隔
let isActive: boolean = true;
let scores: number[] = [95, 87, 92];

1.2 类型推断机制

TypeScript 具有强大的类型推断能力,在大多数情况下可以自动推断变量类型。

// 类型推断示例
let message = "Hello World";        // 推断为 string 类型
let count = 42;                     // 推断为 number 类型  
let isCompleted = false;            // 推断为 boolean 类型
let numbers = [1, 2, 3];            // 推断为 number[] 类型

// 即使没有显式注解,TypeScript 也知道类型
message.toUpperCase();  // ✅ 正确:message 是 string
count.toFixed(2);      // ✅ 正确:count 是 number
// message = 123;      // ❌ 错误:不能将 number 分配给 string

二、变量声明方式详解

2.1 let 声明

let 是现代的块级作用域变量声明方式,推荐在大多数情况下使用。

// 基本用法
let username: string = "Alice";
let userAge: number = 30;

// 重新赋值
username = "Alice Smith";    // ✅ 允许重新赋值
userAge = 31;               // ✅ 允许重新赋值

// 块级作用域
function demonstrateLet() {
    let localVar = "I'm inside function";
    
    if (true) {
        let blockVar = "I'm inside block";
        console.log(blockVar);  // ✅ 可以访问
        console.log(localVar);  // ✅ 可以访问
    }
    
    console.log(localVar);      // ✅ 可以访问
    // console.log(blockVar);   // ❌ 错误:blockVar 在块外不可访问
}

// 循环中的 let
for (let i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i); // 输出 0, 1, 2(每个 i 都是独立的)
    }, 100);
}

2.2 const 声明

const 用于声明常量,一旦赋值就不能重新赋值。但对于对象和数组,其属性或元素仍然可以修改。

// 基本类型常量
const MAX_USERS: number = 100;
const APP_NAME: string = "MyApp";
const IS_PRODUCTION: boolean = false;

// MAX_USERS = 200; // ❌ 错误:不能重新赋值给常量

// 对象常量 - 属性可以修改
const user = {
    name: "John",
    age: 25
};

user.age = 26;          // ✅ 允许:修改对象属性
user.email = "john@example.com"; // ✅ 允许:添加新属性

// user = { name: "Jane" }; // ❌ 错误:不能重新赋值

// 数组常量 - 元素可以修改
const colors: string[] = ["red", "green", "blue"];
colors.push("yellow");  // ✅ 允许:修改数组内容
colors[0] = "crimson";  // ✅ 允许:修改数组元素

// colors = ["purple"]; // ❌ 错误:不能重新赋值

// 使用 readonly 创建真正的不可变对象
interface ReadonlyUser {
    readonly name: string;
    readonly age: number;
}

const readonlyUser: ReadonlyUser = {
    name: "John", 
    age: 25
};

// readonlyUser.age = 26; // ❌ 错误:readonly 属性不能修改

2.3 var 声明(历史遗留)

var 是函数作用域的变量声明,在现代 TypeScript 中不推荐使用。

// var 的函数作用域
function demonstrateVar() {
    if (true) {
        var functionScoped = "I'm function scoped";
        let blockScoped = "I'm block scoped";
    }
    
    console.log(functionScoped); // ✅ 可以访问(变量提升)
    // console.log(blockScoped); // ❌ 错误:blockScoped 不可访问
}

// 变量提升现象
console.log(hoistedVar);    // ✅ 输出:undefined(变量提升)
var hoistedVar = "value";

// console.log(hoistedLet); // ❌ 错误:在声明前使用
// let hoistedLet = "value";

三、类型注解与类型推断

3.1 显式类型注解

// 基本类型注解
let firstName: string = "John";
let userCount: number = 42;
let isLoggedIn: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// 数组类型注解
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];

// 元组类型
let person: [string, number] = ["John", 30];

// 对象类型注解
let user: { name: string; age: number } = {
    name: "Alice",
    age: 25
};

// 联合类型
let id: string | number = "ABC123";
id = 12345; // ✅ 允许

// 任意类型(谨慎使用)
let flexible: any = "could be anything";
flexible = 42;
flexible = true;

// 未知类型(比 any 更安全)
let uncertain: unknown = "Hello";
// uncertain.toUpperCase(); // ❌ 错误:需要类型检查
if (typeof uncertain === "string") {
    console.log(uncertain.toUpperCase()); // ✅ 正确
}

3.2 类型推断的智能场景

// 1. 初始化推断
let message = "Hello";          // 推断为 string
let count = 5;                  // 推断为 number
let isDone = false;             // 推断为 boolean

// 2. 上下文推断
const numbers = [1, 2, 3];
numbers.map(n => n * 2);        // n 推断为 number

// 3. 最佳通用类型推断
const mixed = [1, "hello", true]; // 推断为 (string | number | boolean)[]

// 4. 函数返回类型推断
function add(a: number, b: number) {
    return a + b; // 返回类型推断为 number
}

// 5. 解构推断
const person = { name: "John", age: 30 };
const { name, age } = person;   // name: string, age: number

// 6. 默认值推断
function greet(name = "Anonymous") { // name 推断为 string
    return `Hello, ${name}`;
}

四、高级变量声明模式

4.1 解构赋值

// 数组解构
const numbers: number[] = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
// first: 1, second: 2, rest: [3, 4, 5]

// 对象解构
interface User {
    id: number;
    name: string;
    email: string;
    age?: number;
}

const user: User = {
    id: 1,
    name: "John Doe",
    email: "john@example.com",
    age: 30
};

// 基本解构
const { name, email } = user;

// 重命名
const { name: userName, email: userEmail } = user;

// 默认值
const { name, age = 25 } = user;

// 嵌套解构
const complexUser = {
    id: 1,
    profile: {
        personal: {
            firstName: "John",
            lastName: "Doe"
        },
        contact: {
            email: "john@example.com",
            phone: "123-456-7890"
        }
    }
};

const {
    profile: {
        personal: { firstName, lastName },
        contact: { email: contactEmail }
    }
} = complexUser;

console.log(firstName);      // "John"
console.log(contactEmail);   // "john@example.com"

4.2 类型断言

// 类型断言语法
let someValue: any = "this is a string";

// 语法 1: 尖括号
let strLength1: number = (<string>someValue).length;

// 语法 2: as 语法(推荐)
let strLength2: number = (someValue as string).length;

// 非空断言
function liveDangerously(x?: number | null) {
    console.log(x!.toFixed()); // 非空断言,告诉 TS x 不为 null/undefined
}

// const 断言
const colors = ["red", "green", "blue"] as const;
// colors 类型为 readonly ["red", "green", "blue"]

const user = {
    name: "John",
    age: 30
} as const;
// user 类型为 { readonly name: "John"; readonly age: 30; }

4.3 只读变量和属性

// 使用 const 声明基本类型常量
const APP_VERSION = "1.0.0";
const MAX_CONNECTIONS = 100;

// 使用 readonly 修饰符
interface Config {
    readonly apiUrl: string;
    readonly timeout: number;
    readonly retries: number;
}

const config: Config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
};

// config.apiUrl = "new-url"; // ❌ 错误:只读属性

// 使用 Readonly 工具类型
type ReadonlyUser = Readonly<{
    name: string;
    age: number;
    email: string;
}>;

const readonlyUser: ReadonlyUser = {
    name: "Alice",
    age: 25,
    email: "alice@example.com"
};

// readonlyUser.age = 26; // ❌ 错误:只读属性

// 只读数组
const readOnlyNumbers: readonly number[] = [1, 2, 3];
// readOnlyNumbers.push(4); // ❌ 错误:只读数组
// readOnlyNumbers[0] = 10; // ❌ 错误:只读数组

五、变量声明的最佳实践

5.1 命名约定和代码风格

// 变量命名最佳实践

// ✅ 使用有意义的名称
let customerCount: number = 0;
let isUserAuthenticated: boolean = false;
let maximumFileSize: number = 1024;

// ❌ 避免无意义的名称
let a: number = 0;
let flag: boolean = false;
let temp: number = 1024;

// ✅ 使用 camelCase
let userName: string = "john";
let itemCount: number = 5;
let isActive: boolean = true;

// ✅ 常量使用 UPPER_SNAKE_CASE
const MAX_RETRY_ATTEMPTS: number = 3;
const DEFAULT_TIMEOUT: number = 5000;
const API_BASE_URL: string = "https://api.example.com";

// ✅ 布尔变量使用 is/has/can/should 前缀
let isLoading: boolean = false;
let hasPermission: boolean = true;
let canEdit: boolean = false;
let shouldUpdate: boolean = true;

// ✅ 数组使用复数形式或 List 后缀
let users: User[] = [];
let colorList: string[] = ["red", "green", "blue"];
let errorMessages: string[] = [];

5.2 作用域管理

// 最小化作用域原则
class UserService {
    private users: Map<number, User> = new Map();
    
    // ✅ 好的做法:变量在最小必要作用域中
    getUser(id: number): User | undefined {
        // user 只在函数内需要
        const user = this.users.get(id);
        return user;
    }
    
    // ❌ 不好的做法:不必要的全局变量
    private tempUser: User | undefined;
    
    findUserBad(id: number): User | undefined {
        this.tempUser = this.users.get(id);
        return this.tempUser;
    }
}

// 避免污染全局作用域
(function() {
    // 模块内的私有变量
    const privateConfig = {
        apiKey: "secret",
        endpoint: "/api"
    };
    
    function internalHelper() {
        // 函数内的局部变量
        const temporaryData = processData();
        return temporaryData;
    }
})();

// 使用块级作用域管理资源
function processData(data: string[]) {
    // 使用块级作用域限制变量生命周期
    {
        const temporaryBuffer = Buffer.alloc(1024);
        // 处理数据...
    } // temporaryBuffer 在这里被垃圾回收
    
    {
        const parsedData = JSON.parse(data[0]);
        // 使用 parsedData...
    }
}

5.3 不可变性和状态管理

// 使用 const 和 readonly 实现不可变性
interface AppState {
    readonly user: Readonly<{
        id: number;
        name: string;
        email: string;
    }>;
    readonly settings: Readonly<{
        theme: string;
        language: string;
    }>;
    readonly isLoading: boolean;
}

const initialState: AppState = {
    user: {
        id: 1,
        name: "John Doe",
        email: "john@example.com"
    },
    settings: {
        theme: "dark",
        language: "en"
    },
    isLoading: false
};

// 状态更新模式
function updateUser(state: AppState, updates: Partial<User>): AppState {
    return {
        ...state,
        user: {
            ...state.user,
            ...updates
        }
    };
}

// 使用 Object.freeze 加强不可变性
const frozenConfig = Object.freeze({
    api: {
        baseUrl: "https://api.example.com",
        version: "v1"
    },
    features: {
        darkMode: true,
        notifications: false
    }
});

// frozenConfig.api.baseUrl = "new-url"; // ❌ 运行时错误(严格模式)

六、实际应用场景

6.1 配置管理

// 应用配置变量
class AppConfig {
    // 环境变量
    static readonly NODE_ENV: string = process.env.NODE_ENV || "development";
    static readonly PORT: number = parseInt(process.env.PORT || "3000");
    
    // 数据库配置
    static readonly DATABASE = {
        host: process.env.DB_HOST || "localhost",
        port: parseInt(process.env.DB_PORT || "5432"),
        name: process.env.DB_NAME || "myapp",
        user: process.env.DB_USER || "postgres",
        password: process.env.DB_PASSWORD || "password"
    } as const;
    
    // API 配置
    static readonly API = {
        baseUrl: process.env.API_BASE_URL || "https://api.example.com",
        timeout: parseInt(process.env.API_TIMEOUT || "5000"),
        retries: parseInt(process.env.API_RETRIES || "3")
    } as const;
    
    // 功能开关
    static readonly FEATURES = {
        enableCache: process.env.ENABLE_CACHE === "true",
        enableLogging: process.env.ENABLE_LOGGING !== "false",
        enableAnalytics: process.env.ENABLE_ANALYTICS === "true"
    } as const;
}

// 使用配置
function initializeApp() {
    if (AppConfig.NODE_ENV === "production") {
        console.log("Starting in production mode");
    }
    
    const dbConfig = AppConfig.DATABASE;
    console.log(`Connecting to database: ${dbConfig.host}:${dbConfig.port}`);
    
    if (AppConfig.FEATURES.enableCache) {
        initializeCache();
    }
}

6.2 状态管理

// Redux 风格的状态管理
interface Todo {
    readonly id: number;
    readonly text: string;
    readonly completed: boolean;
}

interface AppState {
    readonly todos: readonly Todo[];
    readonly visibilityFilter: string;
    readonly isLoading: boolean;
}

// 初始状态
const initialState: AppState = {
    todos: [],
    visibilityFilter: "SHOW_ALL",
    isLoading: false
} as const;

// Action 创建器
const addTodo = (text: string) => ({
    type: "ADD_TODO" as const,
    payload: {
        id: Date.now(),
        text,
        completed: false
    }
});

const toggleTodo = (id: number) => ({
    type: "TOGGLE_TODO" as const,
    payload: { id }
});

//  reducer
function todoReducer(state: AppState = initialState, action: any): AppState {
    switch (action.type) {
        case "ADD_TODO":
            return {
                ...state,
                todos: [...state.todos, action.payload]
            };
            
        case "TOGGLE_TODO":
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.payload.id
                        ? { ...todo, completed: !todo.completed }
                        : todo
                )
            };
            
        default:
            return state;
    }
}

6.3 API 客户端

// API 客户端配置
class ApiClient {
    private readonly baseURL: string;
    private readonly timeout: number;
    private readonly defaultHeaders: Record<string, string>;
    
    constructor(config: {
        baseURL: string;
        timeout?: number;
        headers?: Record<string, string>;
    }) {
        this.baseURL = config.baseURL;
        this.timeout = config.timeout || 5000;
        this.defaultHeaders = {
            "Content-Type": "application/json",
            ...config.headers
        };
    }
    
    // 请求方法
    async get<T>(endpoint: string): Promise<T> {
        const url = `${this.baseURL}${endpoint}`;
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.timeout);
        
        try {
            const response = await fetch(url, {
                method: "GET",
                headers: this.defaultHeaders,
                signal: controller.signal
            });
            
            clearTimeout(timeoutId);
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            return await response.json() as T;
        } catch (error) {
            clearTimeout(timeoutId);
            throw error;
        }
    }
    
    async post<T>(endpoint: string, data: any): Promise<T> {
        const url = `${this.baseURL}${endpoint}`;
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.timeout);
        
        try {
            const response = await fetch(url, {
                method: "POST",
                headers: this.defaultHeaders,
                body: JSON.stringify(data),
                signal: controller.signal
            });
            
            clearTimeout(timeoutId);
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            return await response.json() as T;
        } catch (error) {
            clearTimeout(timeoutId);
            throw error;
        }
    }
}

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

const apiClient = new ApiClient({
    baseURL: "https://api.example.com",
    timeout: 10000
});

// 使用类型参数
const users = await apiClient.get<User[]>("/users");
const newUser = await apiClient.post<User>("/users", {
    name: "John Doe",
    email: "john@example.com"
});

七、变量声明决策流程图

graph TD
    A[需要声明变量] --> B{变量是否需要重新赋值}
    
    B -->|不需要| C[使用 const]
    B -->|需要| D[使用 let]
    
    C --> E{是对象/数组吗}
    D --> F{作用域是什么}
    
    E -->|是| G[使用 const + 接口定义]
    E -->|否| H[使用 const + 基本类型]
    
    F -->|块级作用域| I[使用 let]
    F -->|避免使用| J[不使用 var]
    
    G --> K{需要完全不可变吗}
    I --> L[添加类型注解]
    
    K -->|是| M[添加 readonly 修饰符]
    K -->|否| N[允许属性修改]
    
    M --> O[使用 as const 断言]
    N --> P[定义可变接口]
    
    O --> Q[完成声明]
    P --> Q
    H --> Q
    L --> Q

八、常见陷阱和解决方案

8.1 变量提升问题

// 问题:var 的变量提升
function problematicFunction() {
    console.log(hoistedVar); // undefined(不是 ReferenceError)
    var hoistedVar = "value";
    
    // 相当于:
    // var hoistedVar;
    // console.log(hoistedVar);
    // hoistedVar = "value";
}

// 解决方案:使用 let/const
function safeFunction() {
    // console.log(notHoisted); // ❌ 错误:在声明前使用
    let notHoisted = "value";
    console.log(notHoisted); // ✅ 正确
}

8.2 块级作用域陷阱

// 问题:循环中的 var
for (var i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i); // 输出 3, 3, 3(不是 0, 1, 2)
    }, 100);
}

// 解决方案:使用 let
for (let i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i); // 输出 0, 1, 2
    }, 100);
}

8.3 类型推断意外

// 问题:意外的 any 类型
let problem; // 类型推断为 any
problem = "hello";
problem = 42; // 没有错误,但可能不是我们想要的

// 解决方案:显式类型注解
let solution: string; // 明确类型
solution = "hello";
// solution = 42; // ❌ 错误:类型不匹配

// 问题:对象字面量多余属性
interface Point {
    x: number;
    y: number;
}

// const point: Point = { x: 1, y: 2, z: 3 }; // ❌ 错误:多余属性 'z'

// 解决方案:使用类型断言或临时变量
const point1 = { x: 1, y: 2, z: 3 } as Point; // 类型断言
const temp = { x: 1, y: 2, z: 3 };
const point2: Point = temp; // 通过临时变量

九、总结

9.1 关键要点总结

声明方式使用场景特点推荐程度
const常量、配置、不需要重新赋值的变量块级作用域,不可重新赋值⭐⭐⭐⭐⭐
let需要重新赋值的变量、循环计数器块级作用域,可重新赋值⭐⭐⭐⭐
var旧代码兼容函数作用域,变量提升

9.2 最佳实践清单

  1. 优先使用 const,除非变量需要重新赋值
  2. 使用显式类型注解 在无法推断或需要文档化的地方
  3. 利用类型推断 在明显的地方减少冗余代码
  4. 使用有意义的变量名 提高代码可读性
  5. 保持变量作用域最小化 避免不必要的全局变量
  6. 使用 readonlyconst 断言 实现不可变性
  7. 避免 any 类型 除非必要,使用 unknown 代替
  8. 使用解构赋值 简化对象和数组操作

9.3 实用代码模板

// 配置对象模板
const APP_CONFIG = {
    api: {
        baseUrl: "https://api.example.com",
        timeout: 5000
    },
    features: {
        darkMode: true,
        analytics: false
    }
} as const;

// 状态接口模板
interface AppState {
    readonly user: Readonly<{
        id: number;
        name: string;
        email: string;
    }>;
    readonly isLoading: boolean;
    readonly error: string | null;
}

// API 响应模板
interface ApiResponse<T> {
    success: boolean;
    data: T;
    message?: string;
}

// 函数变量模板
const calculateTotal = (items: Array<{ price: number; quantity: number }>): number => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
};

通过掌握这些变量声明技巧和最佳实践,你将能够编写出更加健壮、可维护的 TypeScript 代码。记住,良好的变量声明习惯是高质量代码的基础!

在这里插入图片描述