iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
Modern Web

React Hook 不求人,建立自己的 Hook Libary系列 第 24

[DAY 24] 自己的Hook自己做!useImage 來預覽圖片吧!

  • 分享至 

  • xImage
  •  

今天的主題很單純,就是單純上傳圖片,不論是只有一個頁面或是多個功能都要用到,製造一個 hook 是很不虧的選擇,因為也是懶得再寫一次 (¬_¬)

DEMO 底家

功能

  • 透過 input 上傳後能預覽
  • 能夠取消/刪除
  • 多張圖片一起預覽

開始!

input

首先,要有地方上傳,input 請出場:

<input type="file" id="file-input" accept="image/*" />

type 定義了要上傳圖片,而 accept 只能上傳 image 相關,如果只想限制 jpg,只要修改成 accept=".jpg",相關設定可參閱 MDN

畫面整體會是這樣:

<label htmlFor="file-input">
  <input
    type="file"
    id="file-input"
    accept="image/*"
  />
  <Button as="span">Upload One Image</Button>
</label>

input 加上 display: none & button display: inline 就可以再見原生UI了

hook

function useImage() {
  const inputRef = useRef(null)
  const [image, setImage] = useState(null)

  const handleUpload = (e) => {
    const file = e.target.files[0]
    const imageURL = URL.createObjectURL(file)

    setImage({ name: file.name, url: imageURL })
  }

  const handleRemove = () => {
    URL.revokeObjectURL(image.url)
    setImage(null)
    if (inputRef.current) {
      inputRef.current.value = ""
    }
  }

  return {
    image,
    handleUpload,
    handleRemove,
    inputRef,
  }
}
  • handleUpload 監聽 input onchagne,會接受 event,透過 input 上傳的檔案會出現在 e.target.files 會是一個 FileList Object,可以進一步用array index取值方式取中特定檔案,目前是只能上傳一張圖片,因此 files[0] 即可,而取出來的也會是 File Object,會有檔案名稱、檔案大小等資訊。
  • 使用 URL.createObjectURL,接收File/Blob,回傳 ojbectURL,很易懂的 API,且搭配 URL.revokeObjectURL 在移除圖片時也移除相關 ref 。(API)
  • handleRemove 移除圖片。
  • inputRef 來移除 input.value,雖然 display:none 後看不到 input 本身,但原本的上傳檔案的檔名與路徑會存在 input.value,若只是針對上傳後的圖片進行操作,這個動作就不是那麼必要。

useImages

好,接下來是多張圖片上傳。。

Hook

function useImages() {
  const inputRef = useRef()
  const [images, setImages] = useState(null)

  const handleUpload = (e) => {
    const images = [...e.target.files].map((file) => {
      return {
        name: file.name,
        url: URL.createObjectURL(file),
      }
    })
    setImages(images)
  }

  const handleRemove = (itemIndex) => {
    setImages((prev) =>
      prev.filter((img, index) => {
        if (index === itemIndex) {
          URL.revokeObjectURL(img.url)
        }
        return index !== itemIndex
      })
    )
  }

  useEffect(() => {
    if (images.length === 0) {
      if (inputRef.current) {
        inputRef.current.value = ""
      }
    }
  }, [images])

  return {
    images,
    handleUpload,
    handleRemove,
    inputRef,
  }
}

基本上與前一個一模一樣,只是單一內容操作全部改成 Array 操作,不同的是:

  • input 要記得加上 multiple。
  • 由於 FileList 是唯讀 (readonly),當刪除一張圖片時,沒辦法同時對 inputvalue 或其產生的 FileList 操作,因此才會用 useEffect,當照片都被清除時,才真正清空 value;當然,如果只是針對圖片本身操作,value 清空就不是那麼重要,也可以引導使用者重新上傳(選擇圖片),來確保 value 資料正確。

結語

DEMO 底家

可以再進一步傳入檔案大小上的限制 (請參閱 File.size),來抵擋使用者上傳太大的照片。

我家的餅餅好可愛 (‘∀’●)♡


上一篇
[DAY 23] 自己的 Hook 自己做! useDates 兩個時間恰恰好
下一篇
[DAY 25] 自己的Hook自己做!html2canvas 來擷圖網頁的內容吧!
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言