編輯推薦:稀土掘金,這是一個針對技術開發者的一個應用,你可以在掘金上獲取最新最優質的技術干貨,不僅僅是Android知識、前端、后端以至於產品和設計都有涉獵,想成為全棧工程師的朋友不要錯過!
本文已獲得原作者 Damon Bauer 的授權
譯文出自:掘金翻譯計划
譯者:DeadLion
校對者:mypchas6fans, Kulbear
下面這篇特邀文章是由 Damon Bauer 完成的,主題是關於一個 web 開發人員非常常見的工作:為用戶提供圖片上傳功能。我想說這並不容易,但是有了一些功能強大的工具來幫忙做一些比較“重”的工作,這個任務會覺得比以前輕松許多。Damon 甚至全程在瀏覽器中完成了這項任務!
對於 web 開發者來說,讓用戶能夠上傳圖片是一件很常見的事情。一開始可能看起來小菜一碟,但是當真正創建一個圖片上傳組件的時候,還是有些問題需要去考慮的。這里有一些注意事項:
-
允許什么類型的圖片上傳?
-
需要多大的圖片? 這對性能有何影響?
-
圖片長寬比例應該是多少?
-
如何管理圖片? 能撲捉到不良圖片嗎?
-
圖片存儲在哪? 如何運維?
諸如 Paperclip 和 ImageProcessor 這樣的服務器端工具,能解決上面大部分的問題。不幸的是,目前還沒有一個能用在單頁應用上的現成的工具。我將向你們展示我是如何在一個 React 應用中解決這個問題的,完全沒有用到服務器端語言。
這是我們將要構建的應用的一個小樣品。
工具包
我用到了下面三個工具:
-
react-dropzone 來接受用戶的圖片
-
superagent 轉換上傳的圖片
-
Cloudinary 存儲圖片和編輯圖片。
設置 Cloudinary
Cloudinary 是一個可以為圖片提供存儲、操作、管理、提供功能的雲服務。我選擇使用 Cloudinary 是因為它提供的免費賬戶包含了所有我所需要的功能。你至少需要一個免費帳戶才能開始。
假如說你想裁剪,調整大小並給上傳的圖片增加濾鏡。Cloudinary 有個轉換的概念,和修改圖片功能鏈接在一塊的,不管你需不需要。一旦上傳,就會轉換、修改然后存儲新的圖片。
在 Cloudinary 控制面板中,找到 Settings > Upload,然后選擇 “Upload presets” 下方 的 “Add upload preset”。
下一步,將 “Mode” 改成 “Unsigned”。這是必須的,然后你就可以不需要使用服務器端語言來處理私鑰也能直接上傳到 Cloudinary 了。
在 “Incoming Transformations” 部分選擇 “Edit” 可以添加任何轉換。 你可以裁剪、調整大小、改變質量、旋轉、濾鏡等等。保存預設,這就行了!你現在有地方上傳、處理、存儲圖片了,能夠為你的應用程序提供圖片服務了。注意預設名稱,我們稍后將用到它。讓我們進入代碼部分吧。
接受用戶輸入
為了處理圖片上傳,我用了 react-dropzone 插件。它包含了一些功能,如拖放文件、文件類型限制和多文件上傳。
首先,安裝依賴。在命令行中輸入下面的命令,運行:
npm install react react-dropzone superagent --save
然后在你的組件中導入 React、 react-dropzone 和 superagent。我使用 ES6 import 語法。
import React from 'react'; import Dropzone from 'react-dropzone'; import request from 'superagent';
我們稍后會用到 superagent。現在,在你的組件 render 方法中包含一個react-dropzone 實例。
export default class ContactForm extends React.Component { render() { <Dropzone multiple={false} accept="image/*" onDrop={this.onImageDrop.bind(this)}> <p>Drop an image or click to select a file to upload.</p> </Dropzone> }
以下是這個組件的一些概要:
-
multiple={false} 同一時間只允許一個圖片上傳。
-
accept="image/*" 允許任何類型的圖片。你可以明確的限制文件類型,只允許某些類型可以上傳, 例如 accept="image/jpg,image/png"。
-
onDrop 是一個方法,當圖片被上傳的時候觸發。
當使用 React ES5 類語法(React.createClass),所有方法是 “autobound(自動綁定)” 到類實例上。這篇文章中的代碼使用 ES6 類語法(extends React.Component),不提供自動綁定的。所以我們在 onDrop 屬性中用了 .bind(this) 。(如果你不熟悉 .bind,你可以看看這篇文章了解下。)
處理拖拽圖片
現在,讓我們設置當上傳一個圖像時,做某些事情的方法。
首先,為兩條重要的上傳信息設置一個 const 。
- 上傳預設 ID (當你創建了上傳預設時自動生成)
- 你的 Cloudinary 上傳 URL
// import statements const CLOUDINARY_UPLOAD_PRESET = 'your_upload_preset_id'; const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/your_cloudinary_app_name/upload'; export default class ContactForm extends React.Component { // render()
然后,增加一條記錄到組件初始化 state (使用 this.setState);我給這個屬性起了個名字 uploadedFileCloudinaryUrl。最終,這將存放一個上傳成功后由 Cloudinary 生成的圖片 URL。我們稍后會用到這條 state。
export default class ContactForm extends React.Component { constructor(props) { super(props); this.state = { uploadedFileCloudinaryUrl: '' }; } }
react-dropzone 文檔說它總是返回一個上傳文件的數組,所以我們將該數組傳遞給 onImageDrop 方法的 files 參數。我們設置了一次只能傳一張圖片,所以圖片總是在數組的第一個位置。
調用 handleImageUpload ,將圖片(files[0])傳入該方法。我將這個方法分離出一個單獨的方法,遵循單一職責原則。從本質上講,這一原則方法教你保持方法緊湊,只做一件事。
export default class ContactForm extends React.Component { constructor(props) { ... } onImageDrop(files) { this.setState({ uploadedFile: files[0] }); this.handleImageUpload(files[0]); } render() { ... } }
處理圖片上傳和轉換
首先,用 superagent 將我們之前設置的兩個 const POST 到 Cloudinary 。.field 方法 能讓我們將數據附加到 POST 請求中。這些數據包含了 Cloudinary 處理上傳圖片的所有信息。通過調用 .end,執行請求並提供回調。
export default class ContactForm extends React.Component { constructor(props) { ... } onImageDrop(files) { ... } handleImageUpload(file) { let upload = request.post(CLOUDINARY_UPLOAD_URL) .field('upload_preset', CLOUDINARY_UPLOAD_PRESET) .field('file', file); upload.end((err, response) => { if (err) { console.error(err); } if (response.body.secure_url !== '') { this.setState({ uploadedFileCloudinaryUrl: response.body.secure_url }); } }); } render() { ... } }
在 .end 回調中,打印所有返回錯誤的同時,最好也告訴用戶出現了一個錯誤。
接下來,我們接收到的響應中包含一個 URL,檢查下它是不是一個空字符串。這就是圖片被上傳,處理后 Cloudinary 生成的一個 URL。舉個例子,如果一個用戶正在編輯他的資料,上傳了一張圖片,你可以將 Cloudinary 返回的新的圖片 URL 保存到你的數據庫中。
我們目前寫的代碼,支持用戶拖拽一張圖片,組件將圖片發送到 Cloudinary,然后收到一個給我們用的轉換后的圖片 URL。
渲染階段
組件最后一部分是一個 div,可以預覽上傳后的圖片。
export default class ContactForm extends React.Component { constructor(props) { ... } onImageDrop(files) { ... } handleImageUpload(file) { ... } render() { <div> <div className="FileUpload"> ... </div> <div> {this.state.uploadedFileCloudinaryUrl === '' ? null : <div> <p>{this.state.uploadedFile.name}</p> <img src={this.state.uploadedFileCloudinaryUrl} /> </div> } </div> </div> } }
如果 uploadedFileCloudinaryUrl state 是一個空字符串,三元運算符將輸出null (什么都沒有)。回想下,組件的 uploadedFileCloudinaryUrl state 默認是一個空字符串;這就意味着組件渲染時,這個 div 將是空的。
然而,當 Cloudinary 返回一個 URL,state 不再是空字符串,因為我們在handleImageUpload 更新了 state。此時,該組件將重新渲染,顯示上傳的文件名稱和變換后的圖像的預覽。
結束
This is just the groundwork for an image upload component. There are plenty of additional features you could add, like:
這只是為圖片上傳組件做的准備工作。有很多可以添加的附加功能,比如:
-
允許多圖片上傳
-
清除上傳的圖片
-
如果因為某些原因上傳失敗,展示錯誤
-
使用移動設備相機作為上傳源
目前為止,這些設置已經滿足我工作的需求了。硬編碼上傳預設不是完美的,但我還沒有碰到任何問題。
希望你們已經理解了如何不用服務器端語言,使用 React 就能上傳,存儲和操作圖片。如果你們有任何問題或者點評,我很樂意聽到你們的反饋!我已經建好了一個倉庫,你們可以點擊鏈接查看代碼.