React Native(Expo)中使用jszip库时界面冻结问题求助
解决Expo React Native中ZIP压缩导致UI冻结的问题
问题代码
import JSZip from "jszip"; const createArchive = async() => { // Some code const zip = new JSZip(); const data = getExampleData(); zip.file("example.json", data); const zipContent = await zip.generateAsync( { type: "base64" }, (metadata) => { setCreationProgress(metadata.percent); // Set a progress value for ui console.log("Value:", metadata.percent); } );// When this function is executed the ui freezes // Some code }
问题描述
在基于Expo的React Native项目中,上述代码能正常完成ZIP压缩功能,但执行zip.generateAsync()时,应用界面会完全冻结直到压缩完成。如果处理大文件,耗时会很长,期间界面无法更新进度、也无法交互,用户既看不到压缩进度,也没法取消操作。
试过用expo-task-manager和expo-background-fetch把任务移到后台线程,但没成功;也考虑过更换压缩库,但不清楚哪些适配Expo,求可行的解决方案。
可行解决方案
方案1:用react-native-threads在独立线程执行压缩(适配Expo)
这个库能把CPU密集型任务放到独立线程,避免阻塞UI线程,且支持Expo环境。
- 安装依赖:
npx expo install react-native-threads - 创建独立线程文件(如
zip-thread.js):import JSZip from "jszip"; self.onmessage = async (message) => { const { data } = message; const zip = new JSZip(); zip.file("example.json", data); try { const zipContent = await zip.generateAsync( { type: "base64" }, (metadata) => { self.postMessage({ type: "progress", percent: metadata.percent }); } ); self.postMessage({ type: "complete", content: zipContent }); } catch (error) { self.postMessage({ type: "error", error }); } }; - 主组件中调用线程:
import { Thread } from 'react-native-threads'; const createArchive = async () => { const thread = new Thread('./zip-thread.js'); const data = getExampleData(); thread.postMessage(data); thread.onmessage = (message) => { switch(message.type) { case "progress": setCreationProgress(message.percent); break; case "complete": const zipContent = message.content; // 处理压缩完成后的逻辑 thread.terminate(); // 结束线程释放资源 break; case "error": // 处理错误逻辑 thread.terminate(); break; } }; // 提供取消压缩的方法 window.cancelZipCompression = () => { thread.terminate(); setCreationProgress(0); }; };
方案2:更换为Expo兼容的原生压缩库
原生库的压缩效率远高于JSZip,且不会阻塞UI线程,但部分库需要EAS构建(无法在Expo Go中直接使用)。
选项A:react-native-zip-archive(需EAS构建)
- 安装依赖:
npx expo install react-native-zip-archive - 使用示例(结合
expo-file-system):import { zip } from 'react-native-zip-archive'; import * as FileSystem from 'expo-file-system'; const createArchive = async () => { // 创建临时目录存放待压缩文件 const tempDir = `${FileSystem.cacheDirectory}temp_zip/`; await FileSystem.makeDirectoryAsync(tempDir, { intermediates: true }); // 写入待压缩文件 const data = getExampleData(); await FileSystem.writeAsStringAsync(`${tempDir}example.json`, data); // 目标压缩文件路径 const targetZipPath = `${FileSystem.cacheDirectory}archive.zip`; // 进度回调 const progressCallback = (progress) => { setCreationProgress(progress.percent); }; try { // 执行压缩 await zip(tempDir, targetZipPath, {}, progressCallback); // 读取为base64格式 const zipContent = await FileSystem.readAsStringAsync(targetZipPath, { encoding: 'base64' }); // 处理压缩完成后的逻辑 } catch (error) { // 处理错误 } finally { // 清理临时文件 await FileSystem.deleteAsync(tempDir, { idempotent: true }); await FileSystem.deleteAsync(targetZipPath, { idempotent: true }); } };
选项B:expo-ssziparchive(iOS优先,需EAS构建)
基于iOS原生SSZipArchive的封装,压缩效率高,用法和react-native-zip-archive类似,适合iOS为主的项目。
方案3:优化JSZip的执行(缓解阻塞)
如果不想更换库或使用线程,可通过调整JSZip的参数和进度更新逻辑,减少UI阻塞,但大文件下效果有限:
const createArchive = async () => { const zip = new JSZip(); const data = getExampleData(); zip.file("example.json", data); let lastProgress = 0; const updateProgress = (metadata) => { // 降低进度更新频率,避免UI频繁重绘 if (metadata.percent - lastProgress > 2) { lastProgress = metadata.percent; // 把进度更新放到事件循环末尾,给UI线程留时间处理交互 setTimeout(() => setCreationProgress(lastProgress), 0); } }; // 启用流式处理,减少内存占用同时降低阻塞 const zipContent = await zip.generateAsync( { type: "base64", streamFiles: true }, updateProgress ); };
内容的提问来源于stack exchange,提问作者Oleh Chernysh
相关产品推荐
相关产品推荐

