变量声明是 TypeScript 编程的基础,但其中蕴含着丰富的技巧和最佳实践。本文将深入探讨 TypeScript 中各种变量声明方式,带你从入门到精通。
一、TypeScript 变量声明基础
1.1 变量声明的基本语法
TypeScript 提供了三种主要的变量声明方式:var、let 和 const,结合类型注解实现类型安全。
// 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 最佳实践清单
- 优先使用
const,除非变量需要重新赋值 - 使用显式类型注解 在无法推断或需要文档化的地方
- 利用类型推断 在明显的地方减少冗余代码
- 使用有意义的变量名 提高代码可读性
- 保持变量作用域最小化 避免不必要的全局变量
- 使用
readonly和const断言 实现不可变性 - 避免
any类型 除非必要,使用unknown代替 - 使用解构赋值 简化对象和数组操作
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 代码。记住,良好的变量声明习惯是高质量代码的基础!