TypeScript 中的 never 和 void 类型详解
目录
概述
TypeScript 中的 void 和 never 类型都用于描述函数的返回类型,但它们的语义和使用场景有很大的不同。
void 类型
定义
void 表示函数没有返回值,或者返回 undefined:
- 用于标识函数不返回任何值
- 实际上返回 undefined
- 可以显式返回 undefined
示例
// 基本用法
function logMessage(message: string): void {
console.log(message);
}
// 显式返回 undefined
function doNothing(): void {
return undefined;
}
// 箭头函数
const logError = (error: Error): void => {
console.error(error);
};
// 作为方法
class Logger {
log(message: string): void {
console.log(message);
}
}
特点
- void 类型变量只能赋值 undefined 和 null
- void 函数可以被调用并继续执行后续代码
- 通常用于表示函数的副作用
never 类型
定义
never 表示永远不会返回的函数类型:
- 函数抛出异常
- 函数进入无限循环
- 类型保护中的永远不可能达到的分支
示例
// 抛出异常
function throwError(message: string): never {
throw new Error(message);
}
// 无限循环
function infiniteLoop(): never {
while (true) {
// do something
}
}
// 类型保护中的 never
function checkType(value: string | number): string {
if (typeof value === "string") {
return "string value";
} else if (typeof value === "number") {
return "number value";
} else {
// value 的类型此时是 never
const neverValue: never = value;
return neverValue;
}
}
// 用于详尽性检查
type Shape = Circle | Square;
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default:
// 如果 Shape 类型添加了新的类型但这里没有处理,TypeScript 会报错
return assertNever(shape);
}
}
特点
- never 是所有类型的子类型
- 没有任何类型是 never 的子类型
- 不能给 never 类型变量赋值
- 用于表示永远不会发生的情况
void vs never
主要区别
| 特性 | void | never |
|---|---|---|
| 函数执行 | 会执行完成 | 永不完成 |
| 返回值 | undefined | 无返回值 |
| 后续代码 | 会继续执行 | 不会执行 |
| 类型兼容性 | 可以赋值给 any | 是所有类型的子类型 |
示例对比
// void 示例
function voidFunction(): void {
console.log("This will return undefined");
}
const result = voidFunction(); // result 类型是 void
console.log("This will execute");
// never 示例
function neverFunction(): never {
throw new Error("This will never return");
}
// const result = neverFunction(); // 这行后的代码永远不会执行
console.log("This will not execute");
实际应用场景
void 的应用
- 事件处理器
const handleClick = (event: React.MouseEvent): void => {
event.preventDefault();
// 执行其他操作
};
- 中间件函数
const logMiddleware = (req: Request, res: Response, next: NextFunction): void => {
console.log(`${req.method} ${req.path}`);
next();
};
never 的应用
- 详尽性检查
type UserStatus = "active" | "inactive" | "banned";
function handleStatus(status: UserStatus) {
switch (status) {
case "active":
return "User is active";
case "inactive":
return "User is inactive";
case "banned":
return "User is banned";
default:
// 如果 UserStatus 添加了新状态但这里没有处理,会报错
const _exhaustiveCheck: never = status;
return _exhaustiveCheck;
}
}
- 类型保护
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
type ApiResponse = SuccessResponse | ErrorResponse | LoadingResponse;
function handleResponse(response: ApiResponse) {
if (isSuccess(response)) {
return response.data;
} else if (isError(response)) {
return response.error;
} else if (isLoading(response)) {
return "Loading...";
} else {
// 确保处理了所有可能的情况
return assertNever(response);
}
}
void 与副作用
什么是副作用?
副作用(Side Effects)是指函数除了返回值之外,还对函数外部的状态进行了修改。常见的副作用包括:
- 修改全局变量
- 修改传入的参数
- 进行 API 调用
- 操作 DOM
- 写入文件
- 打印日志
为什么用 void 表示副作用?
- 语义明确
// void 清楚地表明这个函数的目的是执行操作而不是返回值
function logMessage(message: string): void {
console.log(message); // 副作用:打印日志
}
// 这个函数的重点是修改状态,而不是返回值
function updateUserStatus(userId: string, status: string): void {
globalUserStore.update(userId, { status }); // 副作用:修改全局状态
}
- 区分纯函数和副作用函数
// 纯函数:有返回值,没有副作用
function add(a: number, b: number): number {
return a + b;
}
// 副作用函数:没有返回值,但有副作用
function saveToLocalStorage(key: string, value: string): void {
localStorage.setItem(key, value); // 副作用:修改本地存储
}
- React 组件中的事件处理
interface ButtonProps {
// 事件处理函数通常返回 void,因为它们的目的是执行操作
onClick: () => void;
onMouseOver: (event: React.MouseEvent) => void;
}
const Button: React.FC<ButtonProps> = ({ onClick, onMouseOver }) => {
// 这些处理函数通常包含副作用
const handleClick = (): void => {
analytics.trackEvent('button_clicked'); // 副作用:发送分析数据
onClick(); // 副作用:调用父组件方法
};
return <button onClick={handleClick} onMouseOver={onMouseOver}>Click me</button>;
};
- 异步操作中的副作用
// Promise<void> 表明这个异步操作的目的是执行副作用
async function syncUserData(userId: string): Promise<void> {
const userData = await fetchUserData(userId);
await saveToDatabase(userData); // 副作用:保存到数据库
emitEvent('user-synced'); // 副作用:触发事件
}
- 中间件和钩子函数
// Express 中间件
const loggingMiddleware = (
req: Request,
res: Response,
next: NextFunction
): void => {
console.log(`${req.method} ${req.path}`); // 副作用:日志记录
next(); // 副作用:调用下一个中间件
};
// React useEffect
useEffect((): void => {
document.title = `User: ${username}`; // 副作用:修改 DOM
analytics.trackPageView(); // 副作用:发送分析数据
}, [username]);
void 的优势
- 代码意图明确
- void 返回类型明确表示函数的目的是执行操作而不是计算值
- 帮助其他开发者理解代码的意图
- 使代码自文档化
- 类型安全
// TypeScript 会确保你不会意外使用函数的返回值
const result = logMessage("Hello"); // result 的类型是 void
result.toString(); // 编译错误:void 类型没有任何方法
// 防止意外返回值
function updateUI(): void {
document.title = "New Title";
return "done"; // 错误:void 函数不能返回值
}
- 区分函数类别
// 计算函数:返回值
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// 操作函数:执行副作用
function displayTotal(total: number): void {
document.getElementById('total').textContent = `Total: $${total}`;
}
最佳实践
- 显式声明副作用
// 好的实践:明确声明返回 void
function initialize(): void {
setupEventListeners();
loadInitialData();
updateUI();
}
// 避免:隐式返回 undefined
function initialize() {
setupEventListeners();
loadInitialData();
updateUI();
}
- 分离关注点
// 好的实践:分离计算和副作用
function calculateNewTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
function updateTotalDisplay(total: number): void {
document.getElementById('total').textContent = `$${total}`;
}
// 避免:混合计算和副作用
function processTotal(items: Item[]): number {
const total = items.reduce((sum, item) => sum + item.price, 0);
document.getElementById('total').textContent = `$${total}`;
return total;
}
最佳实践
- 使用 void
- 用于不需要返回值的函数
- 用于回调函数
- 用于表示副作用
- 使用 never
- 用于详尽性检查
- 用于表示不可能的情况
- 用于类型保护的穷尽性检查
结论
void和never都是特殊的类型,用于描述函数的返回类型void表示函数执行完成但不返回值never表示函数永远不会正常完成- 正确使用这两种类型可以提高代码的类型安全性和可维护性