Введение: Зачем нужна работа с файловой системой в TypeScript?
Каждое приложение рано или поздно сталкивается с необходимостью взаимодействовать с файловой системой: сохранять конфигурации, обрабатывать загруженные файлы, вести логи или управлять проектами. TypeScript, будучи надстройкой над JavaScript, предоставляет разработчику мощные инструменты для работы с файлами и директориями через модуль fs (File System) из Node.js.
В этой статье мы на практических примерах разберём основные операции с файловой системой в TypeScript: чтение и запись файлов, создание и удаление директорий, а также обработку ошибок. Все примеры будут представлены с использованием строгой типизации, что является ключевым преимуществом TypeScript.
Основные модули для работы с файловой системой
Прежде чем перейти к практике, познакомимся с тремя основными модулями, которые понадобятся для работы:
- fs — основной модуль для операций с файловой системой (чтение, запись, удаление)
- fs/promises — версия модуля
fsс поддержкой промисов (рекомендуется для современного кода) - path — утилита для работы с путями файлов и директорий
Для импорта модулей в TypeScript используйте следующий синтаксис:
import * as fs from 'fs';
import * as fsPromises from 'fs/promises';
import * as path from 'path';
Обратите внимание: для корректной работы с TypeScript убедитесь, что в вашем файле tsconfig.json установлены опции "moduleResolution": "node" и "esModuleInterop": true.
Чтение файлов: синхронный и асинхронный подходы
Синхронное чтение с типизацией
Синхронные методы блокируют выполнение кода до завершения операции. Используйте их только для небольших файлов или в начальной загрузке приложения:
import * as fs from 'fs';
import * as path from 'path';
function readConfigSync(): Record<string, unknown> {
const configPath = path.join(__dirname, 'config.json');
try {
const data = fs.readFileSync(configPath, 'utf-8');
return JSON.parse(data) as Record<string, unknown>;
} catch (error) {
if (error instanceof Error) {
console.error('Ошибка чтения конфига:', error.message);
}
return {};
}
}
const config = readConfigSync();
console.log('Конфигурация загружена:', config);
Асинхронное чтение с async/await (рекомендуемый способ)
Для production-кода всегда используйте асинхронные методы. TypeScript отлично работает с async/await:
import { readFile } from 'fs/promises';
import * as path from 'path';
interface UserData {
id: number;
name: string;
email: string;
}
async function readUserData(fileName: string): Promise<UserData[]> {
const filePath = path.resolve('data', fileName);
try {
const rawData = await readFile(filePath, { encoding: 'utf-8' });
const users: UserData[] = JSON.parse(rawData);
return users;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Не удалось прочитать файл ${fileName}: ${error.message}`);
}
throw error;
}
}
// Использование
(async () => {
try {
const users = await readUserData('users.json');
console.log(`Загружено ${users.length} пользователей`);
} catch (error) {
console.error(error);
}
})();
Запись и обновление файлов
Создание и перезапись файлов
Для записи данных в файл используйте метод writeFile. Он создаст файл, если он не существует, или перезапишет существующий:
import { writeFile, mkdir } from 'fs/promises';
import * as path from 'path';
interface LogEntry {
timestamp: string;
level: 'info' | 'warn' | 'error';
message: string;
}
async function saveLog(entry: LogEntry): Promise<void> {
const logsDir = path.join(process.cwd(), 'logs');
const logFile = path.join(logsDir, 'app.log');
// Создаём директорию, если её нет
await mkdir(logsDir, { recursive: true });
const logLine = JSON.stringify(entry) + '\n';
// append: true добавляет данные в конец файла
await writeFile(logFile, logLine, { flag: 'a' });
console.log('Лог сохранён');
}
// Пример использования
(async () => {
await saveLog({
timestamp: new Date().toISOString(),
level: 'info',
message: 'Приложение запущено'
});
})();
Работа с JSON-файлами: типизированное чтение и запись
Создадим универсальную утилиту для работы с JSON-данными:
import { readFile, writeFile } from 'fs/promises';
import * as path from 'path';
class JsonFileManager<T> {
private filePath: string;
constructor(fileName: string, private defaultValue: T) {
this.filePath = path.resolve('data', fileName);
}
async read(): Promise<T> {
try {
const data = await readFile(this.filePath, 'utf-8');
return JSON.parse(data) as T;
} catch {
// Если файл не существует, возвращаем значение по умолчанию
return this.defaultValue;
}
}
async write(data: T): Promise<void> {
const jsonString = JSON.stringify(data, null, 2);
await writeFile(this.filePath, jsonString, 'utf-8');
}
async update(updater: (current: T) => T): Promise<void> {
const currentData = await this.read();
const updatedData = updater(currentData);
await this.write(updatedData);
}
}
// Использование
interface Settings {
theme: 'light' | 'dark';
fontSize: number;
}
const settingsManager = new JsonFileManager<Settings>('settings.json', {
theme: 'light',
fontSize: 14
});
(async () => {
const settings = await settingsManager.read();
console.log('Текущие настройки:', settings);
await settingsManager.update(current => ({
...current,
theme: 'dark'
}));
})();
Работа с директориями и навигация
Проверка существования и создание папок
Часто необходимо убедиться, что директория существует, прежде чем работать с файлами внутри неё:
import { access, mkdir, readdir } from 'fs/promises';
import * as path from 'path';
async function ensureDirectory(dirPath: string): Promise<boolean> {
try {
await access(dirPath);
return true; // директория существует