渚  直樹

渚 直樹

1637697600

ReactとTypeScriptでファイルドロップゾーンを作成する

ファイルドロップゾーンにはさまざまなソリューションがあります。単純なものもあれば、複雑なものもあります。このチュートリアルは、独自の単純なファイルドロップゾーンを作成するのに役立ちます。さまざまなドラッグアンドドロップイベントを処理する方法、ドロップされたファイルを処理する方法、および再利用可能なドロップゾーンコンポーネント用のシンプルなAPIを作成する方法を学習します

簡単な紹介

このチュートリアルでは、特別な依存関係のない単純なファイルドロップゾーンを最初から作成します。このアプリは、typeScriptテンプレート(フラグ)を使用してcreate-react- appを使用して作成します--template typescript。これにより、ほぼ必要なすべてのリソースが得られます。

デフォルトのReactとTypeScriptの依存関係に加えて、クラス名ライブラリも追加します。このライブラリは、アクティブなときにファイルドロップゾーンにクラスを追加するために使用します。これは、誰かがファイルをその上にドラッグしたときを意味します。このクラスは、ドロップゾーンを強調するためにいくつかのCSSスタイルを適用します。

create-react-appテンプレートを使用すると、削除できるものがいくつか生成されます。これには、のロゴとコンテンツが含まれApp.tsxます。ただし、今のところ、Appコンポーネントのコンテンツはそのままにしておくことができます。後でファイルドロップゾーンとファイルのリストに置き換えます。それでは、ドロップゾーンを見てみましょう。

Dropzoneコンポーネントの作成

カスタムファイルのドロップゾーンコンポーネントの考え方は複雑に見えるかもしれません。しかし、これは必ずしも真実ではありません。ドロップゾーンのロジックでは、いくつかのドラッグアンドドロップイベント、アクティブ状態のいくつかの単純な状態管理、およびドロップされたファイルの処理を処理する必要があります。基本的にはそれだけです。

状態管理には、ReactuseStateフックを使用します。次に、useEffectフックを使用して、イベントリスナーをアタッチし、ドロップゾーンの状態を監視します。最後に、我々はまたしますmemoize使用してすべてのコンポーネントをメモHOCを。構築を始めましょう。

入門

最初に必要なのは、ファイルのドロップゾーンコンポーネントを定義することです。これには、そのprops、またはコンポーネントAPIのインターフェイスの定義も含まれます。ドロップゾーンコンポーネントは、6つのイベントハンドラーを受け入れます。これらのハンドラのうちの4つは、以下のようなイベントに呼び出されますdragenterdragleavedragoverdrop

これらのハンドラーにより、このドロップゾーンコンポーネントを使用するすべてのユーザーが、これらのイベントが発生したときにコードを実行できるようになります。5番目と6番目のハンドラーは合成になります。ドロップゾーンアクティブの状態が変化すると、1つが呼び出されます。これは、誰かがファイルをその上にドラッグしているときと、ドラッグが終わったときを意味します。

これが発生するたびに、このハンドラーが呼び出され、現在のアクティブ/非アクティブ状態を指定するブール値が渡されます。6番目のイベントは、ファイルがドロップゾーンにドロップされたときに呼び出されます。このハンドラーは、ドロップゾーンにドロップされたファイルを渡すため、アプリの他の場所で処理できます。

ドロップゾーン自体は<div>ref。を持つ要素になります。これrefを使用して、コンポーネントがマウントされるときにイベントリスナーをドロップゾーンにアタッチし、コンポーネントがマウント解除されるときにそれらを削除します。このドロップゾーンをより使いやすくするために、子が小道具を通過するように設定します。

これは、コンテンツ自体を削除することなく、このドロップゾーンを他のコンテンツのラッパーとして使用できることを意味します。

import React from 'react'

// Define interface for component props/api:
export interface DropZoneProps {
  onDragStateChange?: (isDragActive: boolean) => void
  onDrag?: () => void
  onDragIn?: () => void
  onDragOut?: () => void
  onDrop?: () => void
  onFilesDrop?: (files: File[]) => void
}

export const DropZone = React.memo(
  (props: React.PropsWithChildren<DropZoneProps>) => {
    const {
      onDragStateChange,
      onFilesDrop,
      onDrag,
      onDragIn,
      onDragOut,
      onDrop,
    } = props

    // Create state to keep track when dropzone is active/non-active:
    const [isDragActive, setIsDragActive] = React.useState(false)
    // Prepare ref for dropzone element:
    const dropZoneRef = React.useRef<null | HTMLDivElement>(null)

    // Render <div> with ref and children:
    return <div ref={dropZoneRef}>{props.children}</div>
  }
)

DropZone.displayName = 'DropZone'

DragEnterイベント

私たちが扱う最初のイベントはdragenterイベントです。このイベントは、ファイルがドロップゾーンに入るとトリガーされ、誰かがファイルを取得してドロップゾーンに配置します。このイベントを使用して、2つのことを行います。まず、onDragIn()propsを介して渡されたオプションのメソッドを呼び出します。

次に、誰かが実際にファイルをドロップゾーン上にドラッグしているかどうかを確認します。その場合、ドロップゾーンのアクティブ状態をに設定しtrueます。また、デフォルトのイベントと伝播を防ぎます。このイベントに必要なのはこれだけです。

// Create handler for dragenter event:
const handleDragIn = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDragIn()":
    onDragIn?.()

    // Check if there are files dragging over the dropzone:
    if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
      // If so, set active state to "true":
      setIsDragActive(true)
    }
  },
  [onDragIn]
)

DragLeaveイベント

dragleaveイベントの処理も非常に簡単になります。このイベントは、一部のファイルがドロップゾーンを離れたときに発生し、その上にホバーしなくなったときに発生します。このイベントを処理するには、いくつかのことを行う必要があります。まず、デフォルトのイベントと伝播を再度防止します。

次に行うことは、onDragOut()propsを介して渡されたオプションのメソッドを呼び出すことです。その後、アクティブ状態をに設定する必要もありfalseます。

// Create handler for dragleave event:
const handleDragOut = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDragOut()":
    onDragOut?.()

    // Set active state to "false":
    setIsDragActive(false)
  },
  [onDragOut]
)

ドラッグイベント

dragoverイベントのハンドラーは、ドロップゾーンのアクティブ状態がtrue何かがドラッグされているときであることを確認するのに役立ちます。ただし、単にアクティブ状態をに設定するだけではありませんtrue。代わりに、最初に現在の状態値がであるかどうかを確認しfalseてから、に変更しtrueます。

これにより、不要な状態の変化を回避できます。また、このイベントを使用しonDrag()て、小道具を介して渡されたメソッドを呼び出します。

// Create handler for dragover event:
const handleDrag = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDrag()":
    onDrag?.()

    // Set active state to "true" if it is not active:
    if (!isDragActive) {
      setIsDragActive(true)
    }
  },
  [isDragActive, onDrag]
)

ドロップイベント

このdropイベントは、私たちが対処する必要のある最も重要なイベントです。そのハンドラーも最長になります。このハンドラーはいくつかのことを行います。まず、デフォルトの動作を防ぎ、伝播を停止します。次に、dropzoneのアクティブ状態をに設定しfalseます。

何かがその領域にドロップされると、ドラッグイベントが終了するため、これは理にかなっています。Dropzoneはこれを登録する必要があります。dropイベントが発生すると、onDrop()propsを介して渡されたオプションのメソッドを呼び出すこともできます。最も重要な部分は、それらのドロップされたファイルです。

それらを処理する前に、まずファイルがあるかどうかを確認します。これは、event.dataTransfer.filesオブジェクトとそのlengthプロパティを確認することで実行できます。いくつかのファイルがある場合はonFilesDrop()、小道具を介して渡されたメソッドを呼び出します。

これにより、ドロップゾーンの外で必要に応じてこれらのファイルを処理できるようになります。これらのファイルをディスパッチすると、dataTransferデータをクリアして、別の用途のためにドロップゾーンを準備できます。ファイルについて重要なことが1つあります。これらのファイルFileListは、配列ではない形式で取得されます。

forループFileListを使用してこれを配列に簡単に変換できます。このループは、dataTransferオブジェクト内のファイルを調べ、それぞれを空の配列にプッシュします。次に、この配列を引数として任意のメソッドonFilesDrop()に渡して、必要な場所にファイルを取得できます。

// Create handler for drop event:
const handleDrop = React.useCallback(
  (event) => {
    event.preventDefault()
    event.stopPropagation()
    // Prevent default events:

    // Set active state to false:
    setIsDragActive(false)
    // Invoke any optional method passed as "onDrop()":
    onDrop?.()

    // If there are any files dropped:
    if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
      // Convert these files to an array:
      const filesToUpload = []

      for (let i = 0; i < event.dataTransfer.files.length; i++) {
        filesToUpload.push(event.dataTransfer.files.item(i))
      }

      // Invoke any optional method passed as "onFilesDrop()", passing array of files as an argument:
      onFilesDrop?.(filesToUpload)

      // Clear transfer data to prepare dropzone for another use:
      event.dataTransfer.clearData()
    }
  },
  [onDrop, onFilesDrop]
)

効果

ハンドラーが完了し、準備が整いました。先に進む前に、2つのuseEffectフックを設定する必要があります。1つのフックは、アクティブ状態を監視するためのものです。この状態が変化したら、onDragStateChange()propsを介して渡されたメソッドを呼び出し、現在の状態値を引数として渡します。

2番目の効果は<div>、マウント時に、作成したすべてのハンドラーをdropzone要素にアタッチします。この後、ドロップゾーンを使用できるようになります。また、このエフェクトを使用して、ドロップゾーンがアンマウントされたときにすべてのイベントリスナーを削除します。これは、クリーンアップ方法で行います。

// Obser active state and emit changes:
React.useEffect(() => {
  onDragStateChange?.(isDragActive)
}, [isDragActive])

// Attach listeners to dropzone on mount:
React.useEffect(() => {
  const tempZoneRef = dropZoneRef?.current
  if (tempZoneRef) {
    tempZoneRef.addEventListener('dragenter', handleDragIn)
    tempZoneRef.addEventListener('dragleave', handleDragOut)
    tempZoneRef.addEventListener('dragover', handleDrag)
    tempZoneRef.addEventListener('drop', handleDrop)
  }

  // Remove listeners from dropzone on unmount:
  return () => {
    tempZoneRef?.removeEventListener('dragenter', handleDragIn)
    tempZoneRef?.removeEventListener('dragleave', handleDragOut)
    tempZoneRef?.removeEventListener('dragover', handleDrag)
    tempZoneRef?.removeEventListener('drop', handleDrop)
  }
}, [])

それを一緒に入れて

これらは、ファイルドロップゾーンコンポーネントに必要なすべてのパーツです。これらすべてのパーツを組み合わせると、Reactアプリのどこでもこのコンポーネントを使用できるようになります。

import React from 'react'

// Define interface for component props/api:
export interface DropZoneProps {
  onDragStateChange?: (isDragActive: boolean) => void
  onDrag?: () => void
  onDragIn?: () => void
  onDragOut?: () => void
  onDrop?: () => void
  onFilesDrop?: (files: File[]) => void
}

export const DropZone = React.memo(
  (props: React.PropsWithChildren<DropZoneProps>) => {
    const {
      onDragStateChange,
      onFilesDrop,
      onDrag,
      onDragIn,
      onDragOut,
      onDrop,
    } = props

    // Create state to keep track when dropzone is active/non-active:
    const [isDragActive, setIsDragActive] = React.useState(false)
    // Prepare ref for dropzone element:
    const dropZoneRef = React.useRef<null | HTMLDivElement>(null)

    // Create helper method to map file list to array of files:
    const mapFileListToArray = (files: FileList) => {
      const array = []

      for (let i = 0; i < files.length; i++) {
        array.push(files.item(i))
      }

      return array
    }

    // Create handler for dragenter event:
    const handleDragIn = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()
        onDragIn?.()

        if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
          setIsDragActive(true)
        }
      },
      [onDragIn]
    )

    // Create handler for dragleave event:
    const handleDragOut = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()
        onDragOut?.()

        setIsDragActive(false)
      },
      [onDragOut]
    )

    // Create handler for dragover event:
    const handleDrag = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()

        onDrag?.()
        if (!isDragActive) {
          setIsDragActive(true)
        }
      },
      [isDragActive, onDrag]
    )

    // Create handler for drop event:
    const handleDrop = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()

        setIsDragActive(false)
        onDrop?.()

        if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
          const files = mapFileListToArray(event.dataTransfer.files)

          onFilesDrop?.(files)
          event.dataTransfer.clearData()
        }
      },
      [onDrop, onFilesDrop]
    )

    // Obser active state and emit changes:
    React.useEffect(() => {
      onDragStateChange?.(isDragActive)
    }, [isDragActive])

    // Attach listeners to dropzone on mount:
    React.useEffect(() => {
      const tempZoneRef = dropZoneRef?.current
      if (tempZoneRef) {
        tempZoneRef.addEventListener('dragenter', handleDragIn)
        tempZoneRef.addEventListener('dragleave', handleDragOut)
        tempZoneRef.addEventListener('dragover', handleDrag)
        tempZoneRef.addEventListener('drop', handleDrop)
      }

      // Remove listeners from dropzone on unmount:
      return () => {
        tempZoneRef?.removeEventListener('dragenter', handleDragIn)
        tempZoneRef?.removeEventListener('dragleave', handleDragOut)
        tempZoneRef?.removeEventListener('dragover', handleDrag)
        tempZoneRef?.removeEventListener('drop', handleDrop)
      }
    }, [])

    // Render <div> with ref and children:
    return <div ref={dropZoneRef}>{props.children}</div>
  }
)

DropZone.displayName = 'DropZone'

単純なファイルリストコンポーネントの追加

ドロップゾーンへの優れたアドオンの1つは、ドロップゾーンにドロップされたすべてのファイルを表示するファイルリストです。これにより、ユーザーはアプリによって登録されたファイルを確認できるため、UIがよりユーザーフレンドリーになります。このリストは複雑である必要はありません。ファイルの名前とサイズだけを表示できます。

このファイルリストコンポーネントは単純です。を介してファイルの配列を受け入れますprops。次に、この配列にマップし<li>、各ファイルの名前とファイルサイズで生成します。すべてのリストアイテムは<ul>要素でラップされます。

import React from 'react'

export interface FileListProps {
  files: File[]
}

export const FileList = React.memo(
  (props: React.PropsWithChildren<FileListProps>) => (
    <ul>
      {props.files.map((file: File) => (
        <li key={`${file.name}_${file.lastModified}`}>
          <span>{file.name}</span>{' '}
          <span>({Math.round(file.size / 1000)}kb)</span>
        </li>
      ))}
    </ul>
  )
)

FileList.displayName = 'FileList'

アプリコンポーネントを作成して機能させる

ファイルドロップゾーンとファイルリストの準備ができました。これは、に移動しApp.tsxてデフォルトのコンテンツを置き換えることができることを意味します。Appコンポーネント内で、2つの状態を作成する必要があります。1つは、ドロップゾーンのアクティブ状態を追跡するためのものです。これを使用して、ドラッグが発生しているときにドロップゾーンを強調表示します。

2番目の状態は、ドロップゾーンにドロップされたファイルの状態になります。また、2つのハンドラーが必要になります。1つはドロップゾーンのonDragStateChange()方法です。このハンドラーを使用して、ローカルのアクティブ状態を更新します。2番目のハンドラーはdropzoneのonFilesDrop()です。

このハンドラーを使用して、ファイルをその外部のドロップゾーンにドロップし、ローカルfiles状態にします。これら両方のハンドラーをDropzoneコンポーネントにアタッチします。ドロップゾーンとファイルリストについては、Appコンポーネントのレンダリングセクションに配置します。

import React from 'react'
import classNames from 'classnames'

// Import dropzone and file list components:
import { DropZone } from './Dropzone'
import { FileList } from './Filelist'

export const App = React.memo(() => {
  // Create "active" state for dropzone:
  const [isDropActive, setIsDropActive] = React.useState(false)
  // Create state for dropped files:
  const [files, setFiles] = React.useState<File[]>([])

  // Create handler for dropzone's onDragStateChange:
  const onDragStateChange = React.useCallback((dragActive: boolean) => {
    setIsDropActive(dragActive)
  }, [])

  // Create handler for dropzone's onFilesDrop:
  const onFilesDrop = React.useCallback((files: File[]) => {
    setFiles(files)
  }, [])

  return (
    <div
      className={classNames('dropZoneWrapper', {
        'dropZoneActive': isDropActive,
      })}
    >
      {/* Render the dropzone */}
      <DropZone onDragStateChange={onDragStateChange} onFilesDrop={onFilesDrop}>
        <h2>Drop your files here</h2>

        {files.length === 0 ? (
          <h3>No files to upload</h3>
        ) : (
          <h3>Files to upload: {files.length}</h3>
        )}

        {/* Render the file list */}
        <FileList files={files} />
      </DropZone>
    </div>
  )
})

App.displayName = 'App'

結論:ReactとTypeScriptでファイルドロップゾーンを作成する方法

あります!カスタムファイルのドロップゾーンコンポーネントを作成しました。これはスタンドアロンコンポーネントであるため、必要な場所で使用できます。このチュートリアルを楽しんでいただけたでしょうか。また、このチュートリアルが、新しくて便利なことを学ぶのに役立つことを願っています。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/react-file-dropzone/

#reactjs 

What is GEEK

Buddha Community

ReactとTypeScriptでファイルドロップゾーンを作成する
渚  直樹

渚 直樹

1637697600

ReactとTypeScriptでファイルドロップゾーンを作成する

ファイルドロップゾーンにはさまざまなソリューションがあります。単純なものもあれば、複雑なものもあります。このチュートリアルは、独自の単純なファイルドロップゾーンを作成するのに役立ちます。さまざまなドラッグアンドドロップイベントを処理する方法、ドロップされたファイルを処理する方法、および再利用可能なドロップゾーンコンポーネント用のシンプルなAPIを作成する方法を学習します

簡単な紹介

このチュートリアルでは、特別な依存関係のない単純なファイルドロップゾーンを最初から作成します。このアプリは、typeScriptテンプレート(フラグ)を使用してcreate-react- appを使用して作成します--template typescript。これにより、ほぼ必要なすべてのリソースが得られます。

デフォルトのReactとTypeScriptの依存関係に加えて、クラス名ライブラリも追加します。このライブラリは、アクティブなときにファイルドロップゾーンにクラスを追加するために使用します。これは、誰かがファイルをその上にドラッグしたときを意味します。このクラスは、ドロップゾーンを強調するためにいくつかのCSSスタイルを適用します。

create-react-appテンプレートを使用すると、削除できるものがいくつか生成されます。これには、のロゴとコンテンツが含まれApp.tsxます。ただし、今のところ、Appコンポーネントのコンテンツはそのままにしておくことができます。後でファイルドロップゾーンとファイルのリストに置き換えます。それでは、ドロップゾーンを見てみましょう。

Dropzoneコンポーネントの作成

カスタムファイルのドロップゾーンコンポーネントの考え方は複雑に見えるかもしれません。しかし、これは必ずしも真実ではありません。ドロップゾーンのロジックでは、いくつかのドラッグアンドドロップイベント、アクティブ状態のいくつかの単純な状態管理、およびドロップされたファイルの処理を処理する必要があります。基本的にはそれだけです。

状態管理には、ReactuseStateフックを使用します。次に、useEffectフックを使用して、イベントリスナーをアタッチし、ドロップゾーンの状態を監視します。最後に、我々はまたしますmemoize使用してすべてのコンポーネントをメモHOCを。構築を始めましょう。

入門

最初に必要なのは、ファイルのドロップゾーンコンポーネントを定義することです。これには、そのprops、またはコンポーネントAPIのインターフェイスの定義も含まれます。ドロップゾーンコンポーネントは、6つのイベントハンドラーを受け入れます。これらのハンドラのうちの4つは、以下のようなイベントに呼び出されますdragenterdragleavedragoverdrop

これらのハンドラーにより、このドロップゾーンコンポーネントを使用するすべてのユーザーが、これらのイベントが発生したときにコードを実行できるようになります。5番目と6番目のハンドラーは合成になります。ドロップゾーンアクティブの状態が変化すると、1つが呼び出されます。これは、誰かがファイルをその上にドラッグしているときと、ドラッグが終わったときを意味します。

これが発生するたびに、このハンドラーが呼び出され、現在のアクティブ/非アクティブ状態を指定するブール値が渡されます。6番目のイベントは、ファイルがドロップゾーンにドロップされたときに呼び出されます。このハンドラーは、ドロップゾーンにドロップされたファイルを渡すため、アプリの他の場所で処理できます。

ドロップゾーン自体は<div>ref。を持つ要素になります。これrefを使用して、コンポーネントがマウントされるときにイベントリスナーをドロップゾーンにアタッチし、コンポーネントがマウント解除されるときにそれらを削除します。このドロップゾーンをより使いやすくするために、子が小道具を通過するように設定します。

これは、コンテンツ自体を削除することなく、このドロップゾーンを他のコンテンツのラッパーとして使用できることを意味します。

import React from 'react'

// Define interface for component props/api:
export interface DropZoneProps {
  onDragStateChange?: (isDragActive: boolean) => void
  onDrag?: () => void
  onDragIn?: () => void
  onDragOut?: () => void
  onDrop?: () => void
  onFilesDrop?: (files: File[]) => void
}

export const DropZone = React.memo(
  (props: React.PropsWithChildren<DropZoneProps>) => {
    const {
      onDragStateChange,
      onFilesDrop,
      onDrag,
      onDragIn,
      onDragOut,
      onDrop,
    } = props

    // Create state to keep track when dropzone is active/non-active:
    const [isDragActive, setIsDragActive] = React.useState(false)
    // Prepare ref for dropzone element:
    const dropZoneRef = React.useRef<null | HTMLDivElement>(null)

    // Render <div> with ref and children:
    return <div ref={dropZoneRef}>{props.children}</div>
  }
)

DropZone.displayName = 'DropZone'

DragEnterイベント

私たちが扱う最初のイベントはdragenterイベントです。このイベントは、ファイルがドロップゾーンに入るとトリガーされ、誰かがファイルを取得してドロップゾーンに配置します。このイベントを使用して、2つのことを行います。まず、onDragIn()propsを介して渡されたオプションのメソッドを呼び出します。

次に、誰かが実際にファイルをドロップゾーン上にドラッグしているかどうかを確認します。その場合、ドロップゾーンのアクティブ状態をに設定しtrueます。また、デフォルトのイベントと伝播を防ぎます。このイベントに必要なのはこれだけです。

// Create handler for dragenter event:
const handleDragIn = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDragIn()":
    onDragIn?.()

    // Check if there are files dragging over the dropzone:
    if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
      // If so, set active state to "true":
      setIsDragActive(true)
    }
  },
  [onDragIn]
)

DragLeaveイベント

dragleaveイベントの処理も非常に簡単になります。このイベントは、一部のファイルがドロップゾーンを離れたときに発生し、その上にホバーしなくなったときに発生します。このイベントを処理するには、いくつかのことを行う必要があります。まず、デフォルトのイベントと伝播を再度防止します。

次に行うことは、onDragOut()propsを介して渡されたオプションのメソッドを呼び出すことです。その後、アクティブ状態をに設定する必要もありfalseます。

// Create handler for dragleave event:
const handleDragOut = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDragOut()":
    onDragOut?.()

    // Set active state to "false":
    setIsDragActive(false)
  },
  [onDragOut]
)

ドラッグイベント

dragoverイベントのハンドラーは、ドロップゾーンのアクティブ状態がtrue何かがドラッグされているときであることを確認するのに役立ちます。ただし、単にアクティブ状態をに設定するだけではありませんtrue。代わりに、最初に現在の状態値がであるかどうかを確認しfalseてから、に変更しtrueます。

これにより、不要な状態の変化を回避できます。また、このイベントを使用しonDrag()て、小道具を介して渡されたメソッドを呼び出します。

// Create handler for dragover event:
const handleDrag = React.useCallback(
  (event) => {
    // Prevent default events:
    event.preventDefault()
    event.stopPropagation()
    // Invoke any optional method passed as "onDrag()":
    onDrag?.()

    // Set active state to "true" if it is not active:
    if (!isDragActive) {
      setIsDragActive(true)
    }
  },
  [isDragActive, onDrag]
)

ドロップイベント

このdropイベントは、私たちが対処する必要のある最も重要なイベントです。そのハンドラーも最長になります。このハンドラーはいくつかのことを行います。まず、デフォルトの動作を防ぎ、伝播を停止します。次に、dropzoneのアクティブ状態をに設定しfalseます。

何かがその領域にドロップされると、ドラッグイベントが終了するため、これは理にかなっています。Dropzoneはこれを登録する必要があります。dropイベントが発生すると、onDrop()propsを介して渡されたオプションのメソッドを呼び出すこともできます。最も重要な部分は、それらのドロップされたファイルです。

それらを処理する前に、まずファイルがあるかどうかを確認します。これは、event.dataTransfer.filesオブジェクトとそのlengthプロパティを確認することで実行できます。いくつかのファイルがある場合はonFilesDrop()、小道具を介して渡されたメソッドを呼び出します。

これにより、ドロップゾーンの外で必要に応じてこれらのファイルを処理できるようになります。これらのファイルをディスパッチすると、dataTransferデータをクリアして、別の用途のためにドロップゾーンを準備できます。ファイルについて重要なことが1つあります。これらのファイルFileListは、配列ではない形式で取得されます。

forループFileListを使用してこれを配列に簡単に変換できます。このループは、dataTransferオブジェクト内のファイルを調べ、それぞれを空の配列にプッシュします。次に、この配列を引数として任意のメソッドonFilesDrop()に渡して、必要な場所にファイルを取得できます。

// Create handler for drop event:
const handleDrop = React.useCallback(
  (event) => {
    event.preventDefault()
    event.stopPropagation()
    // Prevent default events:

    // Set active state to false:
    setIsDragActive(false)
    // Invoke any optional method passed as "onDrop()":
    onDrop?.()

    // If there are any files dropped:
    if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
      // Convert these files to an array:
      const filesToUpload = []

      for (let i = 0; i < event.dataTransfer.files.length; i++) {
        filesToUpload.push(event.dataTransfer.files.item(i))
      }

      // Invoke any optional method passed as "onFilesDrop()", passing array of files as an argument:
      onFilesDrop?.(filesToUpload)

      // Clear transfer data to prepare dropzone for another use:
      event.dataTransfer.clearData()
    }
  },
  [onDrop, onFilesDrop]
)

効果

ハンドラーが完了し、準備が整いました。先に進む前に、2つのuseEffectフックを設定する必要があります。1つのフックは、アクティブ状態を監視するためのものです。この状態が変化したら、onDragStateChange()propsを介して渡されたメソッドを呼び出し、現在の状態値を引数として渡します。

2番目の効果は<div>、マウント時に、作成したすべてのハンドラーをdropzone要素にアタッチします。この後、ドロップゾーンを使用できるようになります。また、このエフェクトを使用して、ドロップゾーンがアンマウントされたときにすべてのイベントリスナーを削除します。これは、クリーンアップ方法で行います。

// Obser active state and emit changes:
React.useEffect(() => {
  onDragStateChange?.(isDragActive)
}, [isDragActive])

// Attach listeners to dropzone on mount:
React.useEffect(() => {
  const tempZoneRef = dropZoneRef?.current
  if (tempZoneRef) {
    tempZoneRef.addEventListener('dragenter', handleDragIn)
    tempZoneRef.addEventListener('dragleave', handleDragOut)
    tempZoneRef.addEventListener('dragover', handleDrag)
    tempZoneRef.addEventListener('drop', handleDrop)
  }

  // Remove listeners from dropzone on unmount:
  return () => {
    tempZoneRef?.removeEventListener('dragenter', handleDragIn)
    tempZoneRef?.removeEventListener('dragleave', handleDragOut)
    tempZoneRef?.removeEventListener('dragover', handleDrag)
    tempZoneRef?.removeEventListener('drop', handleDrop)
  }
}, [])

それを一緒に入れて

これらは、ファイルドロップゾーンコンポーネントに必要なすべてのパーツです。これらすべてのパーツを組み合わせると、Reactアプリのどこでもこのコンポーネントを使用できるようになります。

import React from 'react'

// Define interface for component props/api:
export interface DropZoneProps {
  onDragStateChange?: (isDragActive: boolean) => void
  onDrag?: () => void
  onDragIn?: () => void
  onDragOut?: () => void
  onDrop?: () => void
  onFilesDrop?: (files: File[]) => void
}

export const DropZone = React.memo(
  (props: React.PropsWithChildren<DropZoneProps>) => {
    const {
      onDragStateChange,
      onFilesDrop,
      onDrag,
      onDragIn,
      onDragOut,
      onDrop,
    } = props

    // Create state to keep track when dropzone is active/non-active:
    const [isDragActive, setIsDragActive] = React.useState(false)
    // Prepare ref for dropzone element:
    const dropZoneRef = React.useRef<null | HTMLDivElement>(null)

    // Create helper method to map file list to array of files:
    const mapFileListToArray = (files: FileList) => {
      const array = []

      for (let i = 0; i < files.length; i++) {
        array.push(files.item(i))
      }

      return array
    }

    // Create handler for dragenter event:
    const handleDragIn = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()
        onDragIn?.()

        if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
          setIsDragActive(true)
        }
      },
      [onDragIn]
    )

    // Create handler for dragleave event:
    const handleDragOut = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()
        onDragOut?.()

        setIsDragActive(false)
      },
      [onDragOut]
    )

    // Create handler for dragover event:
    const handleDrag = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()

        onDrag?.()
        if (!isDragActive) {
          setIsDragActive(true)
        }
      },
      [isDragActive, onDrag]
    )

    // Create handler for drop event:
    const handleDrop = React.useCallback(
      (event) => {
        event.preventDefault()
        event.stopPropagation()

        setIsDragActive(false)
        onDrop?.()

        if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
          const files = mapFileListToArray(event.dataTransfer.files)

          onFilesDrop?.(files)
          event.dataTransfer.clearData()
        }
      },
      [onDrop, onFilesDrop]
    )

    // Obser active state and emit changes:
    React.useEffect(() => {
      onDragStateChange?.(isDragActive)
    }, [isDragActive])

    // Attach listeners to dropzone on mount:
    React.useEffect(() => {
      const tempZoneRef = dropZoneRef?.current
      if (tempZoneRef) {
        tempZoneRef.addEventListener('dragenter', handleDragIn)
        tempZoneRef.addEventListener('dragleave', handleDragOut)
        tempZoneRef.addEventListener('dragover', handleDrag)
        tempZoneRef.addEventListener('drop', handleDrop)
      }

      // Remove listeners from dropzone on unmount:
      return () => {
        tempZoneRef?.removeEventListener('dragenter', handleDragIn)
        tempZoneRef?.removeEventListener('dragleave', handleDragOut)
        tempZoneRef?.removeEventListener('dragover', handleDrag)
        tempZoneRef?.removeEventListener('drop', handleDrop)
      }
    }, [])

    // Render <div> with ref and children:
    return <div ref={dropZoneRef}>{props.children}</div>
  }
)

DropZone.displayName = 'DropZone'

単純なファイルリストコンポーネントの追加

ドロップゾーンへの優れたアドオンの1つは、ドロップゾーンにドロップされたすべてのファイルを表示するファイルリストです。これにより、ユーザーはアプリによって登録されたファイルを確認できるため、UIがよりユーザーフレンドリーになります。このリストは複雑である必要はありません。ファイルの名前とサイズだけを表示できます。

このファイルリストコンポーネントは単純です。を介してファイルの配列を受け入れますprops。次に、この配列にマップし<li>、各ファイルの名前とファイルサイズで生成します。すべてのリストアイテムは<ul>要素でラップされます。

import React from 'react'

export interface FileListProps {
  files: File[]
}

export const FileList = React.memo(
  (props: React.PropsWithChildren<FileListProps>) => (
    <ul>
      {props.files.map((file: File) => (
        <li key={`${file.name}_${file.lastModified}`}>
          <span>{file.name}</span>{' '}
          <span>({Math.round(file.size / 1000)}kb)</span>
        </li>
      ))}
    </ul>
  )
)

FileList.displayName = 'FileList'

アプリコンポーネントを作成して機能させる

ファイルドロップゾーンとファイルリストの準備ができました。これは、に移動しApp.tsxてデフォルトのコンテンツを置き換えることができることを意味します。Appコンポーネント内で、2つの状態を作成する必要があります。1つは、ドロップゾーンのアクティブ状態を追跡するためのものです。これを使用して、ドラッグが発生しているときにドロップゾーンを強調表示します。

2番目の状態は、ドロップゾーンにドロップされたファイルの状態になります。また、2つのハンドラーが必要になります。1つはドロップゾーンのonDragStateChange()方法です。このハンドラーを使用して、ローカルのアクティブ状態を更新します。2番目のハンドラーはdropzoneのonFilesDrop()です。

このハンドラーを使用して、ファイルをその外部のドロップゾーンにドロップし、ローカルfiles状態にします。これら両方のハンドラーをDropzoneコンポーネントにアタッチします。ドロップゾーンとファイルリストについては、Appコンポーネントのレンダリングセクションに配置します。

import React from 'react'
import classNames from 'classnames'

// Import dropzone and file list components:
import { DropZone } from './Dropzone'
import { FileList } from './Filelist'

export const App = React.memo(() => {
  // Create "active" state for dropzone:
  const [isDropActive, setIsDropActive] = React.useState(false)
  // Create state for dropped files:
  const [files, setFiles] = React.useState<File[]>([])

  // Create handler for dropzone's onDragStateChange:
  const onDragStateChange = React.useCallback((dragActive: boolean) => {
    setIsDropActive(dragActive)
  }, [])

  // Create handler for dropzone's onFilesDrop:
  const onFilesDrop = React.useCallback((files: File[]) => {
    setFiles(files)
  }, [])

  return (
    <div
      className={classNames('dropZoneWrapper', {
        'dropZoneActive': isDropActive,
      })}
    >
      {/* Render the dropzone */}
      <DropZone onDragStateChange={onDragStateChange} onFilesDrop={onFilesDrop}>
        <h2>Drop your files here</h2>

        {files.length === 0 ? (
          <h3>No files to upload</h3>
        ) : (
          <h3>Files to upload: {files.length}</h3>
        )}

        {/* Render the file list */}
        <FileList files={files} />
      </DropZone>
    </div>
  )
})

App.displayName = 'App'

結論:ReactとTypeScriptでファイルドロップゾーンを作成する方法

あります!カスタムファイルのドロップゾーンコンポーネントを作成しました。これはスタンドアロンコンポーネントであるため、必要な場所で使用できます。このチュートリアルを楽しんでいただけたでしょうか。また、このチュートリアルが、新しくて便利なことを学ぶのに役立つことを願っています。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/react-file-dropzone/

#reactjs