我很難找到關於如何在基於 Expo 的行動應用程式中上傳和下載檔案的清晰範例。為了幫助面臨同樣挑戰的其他人或任何只是好奇的人,我寫了這篇文章。
在這個過程中,我們將探索有助於理解的關鍵概念:
我們將要涵蓋的內容:
所有程式碼和Postman集合都可以在我的GitHub上找到。
伺服器在Fastify(Express.js 的現代化版本)上運作。要啟動應用程序,請執行以下操作:
在 app.js 中,我們有三個關鍵端點:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
此端點使用 createReadStream() 將 example.webp 傳送為流。包含 MIME 類型,以便用戶端知道如何處理該檔案。例如.webp,這將是image/webp。
?注意:MIME 類型定義了正在傳送的文件的格式。這有助於客戶端正確顯示它。
查看更多 MIME 類型。
Content-Disposition 標頭定義如何將內容呈現給客戶端。包括附件;文件名=;提示瀏覽器下載文件而不是內聯顯示它。若要直接顯示它,請使用內聯而不是附件。
了解更多有關內容處置的資訊
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
此端點接受多部分/表單資料請求。它:
例如,請求可能如下所示:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
此端點期望請求正文中有一個二進位檔案(應用程式/八位元組流)。與multipart/form-data不同的是,該檔案已經是二進位資料了,所以我們可以直接將其寫入磁碟。
請求在 Postman 中看起來像這樣:
運行應用程式:
Web 應用程式的所有功能都包含在 App.tsx 中:
這個 React 應用程式提供三個關鍵功能:
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
當使用者點擊「下載」按鈕時,應用程式:
行為取決於伺服器傳回的 Content-Disposition 標頭:
為了觸發下載,應用程式會建立一個臨時的 ; href 設定為 objectURL 的元素並以程式設計方式點擊它,模擬使用者下載操作。
fastify.post("/upload-octet-stream", async function handler(request) { const filename = request.headers["x-file-name"] ?? "unknown.text"; const data = request.body; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, data); return { uploaded: true }; });
點選「上傳檔案」按鈕時:
這使得伺服器能夠正確處理和保存上傳的檔案。
const downloadFile = async () => { const response = await fetch(DOWNLOAD_API); if (!response.ok) throw new Error("Failed to download file"); const blob = await response.blob(); const contentDisposition = response.headers.get("Content-Disposition"); const isInline = contentDisposition?.split(";")[0] === "inline"; const filename = contentDisposition?.split("filename=")[1]; const url = window.URL.createObjectURL(blob); if (isInline) { window.open(url, "_blank"); } else { const a = document.createElement("a"); a.href = url; a.download = filename || "file.txt"; a.click(); } window.URL.revokeObjectURL(url); };
這種方法比使用 multipart/form-data 更簡單 - 只需將檔案作為二進位資料直接在請求正文中發送,並將檔案名稱包含在請求標頭中。
您可以使用以下命令啟動應用程式:
主要邏輯位於 App.tsx 中,它呈現以下內容:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
要在新視圖中顯示檔案(就像瀏覽器在新分頁中開啟檔案一樣),我們必須將回應作為 blob 讀取,然後使用 FileReader 將其轉換為 base64。
我們將檔案寫入快取目錄(只有應用程式可以存取的私有目錄),然後使用 IntentLauncher 或共用(如果使用者使用 iOS)顯示它。
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
這與 Web 進程類似,但我們必須使用 FileReader 將 blob 讀取為 base64,然後請求權限將檔案下載到使用者想要儲存檔案的位置。
fastify.post("/upload-octet-stream", async function handler(request) { const filename = request.headers["x-file-name"] ?? "unknown.text"; const data = request.body; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, data); return { uploaded: true }; });
使用 DocumentPicker 使用戶能夠選擇文件,然後使用 FormData 將所選文件附加到請求中。過程非常簡單。
const downloadFile = async () => { const response = await fetch(DOWNLOAD_API); if (!response.ok) throw new Error("Failed to download file"); const blob = await response.blob(); const contentDisposition = response.headers.get("Content-Disposition"); const isInline = contentDisposition?.split(";")[0] === "inline"; const filename = contentDisposition?.split("filename=")[1]; const url = window.URL.createObjectURL(blob); if (isInline) { window.open(url, "_blank"); } else { const a = document.createElement("a"); a.href = url; a.download = filename || "file.txt"; a.click(); } window.URL.revokeObjectURL(url); };
作為 Application/octet-stream 上傳比使用 FormData 更簡單:使用文件詳細資訊和內容類型設定標頭,然後將文件新增至請求正文,就是這樣!
如何在平台之間查看、下載和上傳檔案可能有點令人困惑,在這篇文章中我們看到了最常見的。
希望對您有幫助?
讓我在@twitter上
以上是React & Expo - 如何上傳下載文件的詳細內容。更多資訊請關注PHP中文網其他相關文章!