앱에서 PDF나 이미지를 받아서 기기에 저장할 일이 생겼다. expo-file-system을 오랜만에 열었더니 API가 통째로 바뀌어 있었다. FileSystem.downloadAsync, documentDirectory 같은 함수형 API 대신 File과 Directory 클래스가 들어왔다.
뭐가 달라졌나
기존 API는 죄다 비동기에 경로가 문자열이었다. 파일이 있는지 확인하고, URI를 직접 조합하고, getInfoAsync로 크기를 캐내는 식이었다.
// legacy — 문자열 경로 + 비동기 범벅
import * as FileSystem from 'expo-file-system';
const uri = FileSystem.cacheDirectory + 'report.pdf';
const { exists } = await FileSystem.getInfoAsync(uri);
if (!exists) {
await FileSystem.downloadAsync('https://example.com/report.pdf', uri);
}
새 API는 파일을 객체로 다룬다. 존재 여부나 크기 같은 건 동기 프로퍼티로 바로 읽힌다.
import { File, Paths } from 'expo-file-system';
const file = new File(Paths.cache, 'report.pdf');
if (!file.exists) {
await file.downloadFileAsync('https://example.com/report.pdf');
}
console.log(file.size, file.uri); // 동기로 접근
Paths.cache, Paths.document가 기존 cacheDirectory, documentDirectory를 대신한다. 경로를 +로 붙이던 걸 생성자 인자로 넘기니 슬래시 빠뜨려서 삽질할 일이 없어졌다.
디렉터리 만들고 파일 쓰기
다운로드받은 파일을 따로 폴더에 정리하고 싶을 때도 클래스로 처리한다.
import { File, Directory, Paths } from 'expo-file-system';
const dir = new Directory(Paths.document, 'invoices');
if (!dir.exists) dir.create();
const file = new File(dir, 'jan.json');
file.write(JSON.stringify({ total: 42000 }));
const text = file.text(); // 읽기도 동기
console.log(JSON.parse(text));
write, text, bytes, delete 전부 동기다. 작은 JSON이나 설정 파일 다룰 때 await을 안 붙여도 되니 코드가 훨씬 납작해진다. 용량 큰 다운로드만 downloadFileAsync로 비동기 처리하면 된다.
마이그레이션 팁
한 번에 다 갈아엎을 필요는 없다. 예전 코드는 expo-file-system/legacy에서 그대로 import 하면 계속 돌아간다.
import * as FileSystem from 'expo-file-system/legacy';
그래서 새로 짜는 화면부터 File/Directory로 쓰고, 기존 코드는 legacy 경로로 유지하다가 천천히 옮기는 전략이 안전하다.
핵심은 "파일을 문자열 경로가 아니라 객체로 본다"는 것 하나다. 존재 확인·크기·읽기가 동기 프로퍼티로 정리되니, 예전처럼 await을 줄줄이 달던 코드가 눈에 띄게 짧아진다. 새 프로젝트라면 처음부터 새 API로 시작하는 걸 추천한다.