1637697600
ファイルドロップゾーンにはさまざまなソリューションがあります。単純なものもあれば、複雑なものもあります。このチュートリアルは、独自の単純なファイルドロップゾーンを作成するのに役立ちます。さまざまなドラッグアンドドロップイベントを処理する方法、ドロップされたファイルを処理する方法、および再利用可能なドロップゾーンコンポーネント用のシンプルなAPIを作成する方法を学習します
このチュートリアルでは、特別な依存関係のない単純なファイルドロップゾーンを最初から作成します。このアプリは、typeScriptテンプレート(フラグ)を使用してcreate-react- appを使用して作成します--template typescript
。これにより、ほぼ必要なすべてのリソースが得られます。
デフォルトのReactとTypeScriptの依存関係に加えて、クラス名ライブラリも追加します。このライブラリは、アクティブなときにファイルドロップゾーンにクラスを追加するために使用します。これは、誰かがファイルをその上にドラッグしたときを意味します。このクラスは、ドロップゾーンを強調するためにいくつかのCSSスタイルを適用します。
create-react-app
テンプレートを使用すると、削除できるものがいくつか生成されます。これには、のロゴとコンテンツが含まれApp.tsx
ます。ただし、今のところ、Appコンポーネントのコンテンツはそのままにしておくことができます。後でファイルドロップゾーンとファイルのリストに置き換えます。それでは、ドロップゾーンを見てみましょう。
カスタムファイルのドロップゾーンコンポーネントの考え方は複雑に見えるかもしれません。しかし、これは必ずしも真実ではありません。ドロップゾーンのロジックでは、いくつかのドラッグアンドドロップイベント、アクティブ状態のいくつかの単純な状態管理、およびドロップされたファイルの処理を処理する必要があります。基本的にはそれだけです。
状態管理には、ReactuseStateフックを使用します。次に、useEffectフックを使用して、イベントリスナーをアタッチし、ドロップゾーンの状態を監視します。最後に、我々はまたしますmemoize使用してすべてのコンポーネントをメモHOCを。構築を始めましょう。
最初に必要なのは、ファイルのドロップゾーンコンポーネントを定義することです。これには、そのprops
、またはコンポーネントAPIのインターフェイスの定義も含まれます。ドロップゾーンコンポーネントは、6つのイベントハンドラーを受け入れます。これらのハンドラのうちの4つは、以下のようなイベントに呼び出されますdragenter
、dragleave
、dragover
とdrop
。
これらのハンドラーにより、このドロップゾーンコンポーネントを使用するすべてのユーザーが、これらのイベントが発生したときにコードを実行できるようになります。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
イベントです。このイベントは、ファイルがドロップゾーンに入るとトリガーされ、誰かがファイルを取得してドロップゾーンに配置します。このイベントを使用して、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
イベントの処理も非常に簡単になります。このイベントは、一部のファイルがドロップゾーンを離れたときに発生し、その上にホバーしなくなったときに発生します。このイベントを処理するには、いくつかのことを行う必要があります。まず、デフォルトのイベントと伝播を再度防止します。
次に行うことは、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'
あります!カスタムファイルのドロップゾーンコンポーネントを作成しました。これはスタンドアロンコンポーネントであるため、必要な場所で使用できます。このチュートリアルを楽しんでいただけたでしょうか。また、このチュートリアルが、新しくて便利なことを学ぶのに役立つことを願っています。
この記事が気に入ったら、今後の投稿を見逃さないように購読してください。
リンク: https://blog.alexdevero.com/react-file-dropzone/
1637697600
ファイルドロップゾーンにはさまざまなソリューションがあります。単純なものもあれば、複雑なものもあります。このチュートリアルは、独自の単純なファイルドロップゾーンを作成するのに役立ちます。さまざまなドラッグアンドドロップイベントを処理する方法、ドロップされたファイルを処理する方法、および再利用可能なドロップゾーンコンポーネント用のシンプルなAPIを作成する方法を学習します
このチュートリアルでは、特別な依存関係のない単純なファイルドロップゾーンを最初から作成します。このアプリは、typeScriptテンプレート(フラグ)を使用してcreate-react- appを使用して作成します--template typescript
。これにより、ほぼ必要なすべてのリソースが得られます。
デフォルトのReactとTypeScriptの依存関係に加えて、クラス名ライブラリも追加します。このライブラリは、アクティブなときにファイルドロップゾーンにクラスを追加するために使用します。これは、誰かがファイルをその上にドラッグしたときを意味します。このクラスは、ドロップゾーンを強調するためにいくつかのCSSスタイルを適用します。
create-react-app
テンプレートを使用すると、削除できるものがいくつか生成されます。これには、のロゴとコンテンツが含まれApp.tsx
ます。ただし、今のところ、Appコンポーネントのコンテンツはそのままにしておくことができます。後でファイルドロップゾーンとファイルのリストに置き換えます。それでは、ドロップゾーンを見てみましょう。
カスタムファイルのドロップゾーンコンポーネントの考え方は複雑に見えるかもしれません。しかし、これは必ずしも真実ではありません。ドロップゾーンのロジックでは、いくつかのドラッグアンドドロップイベント、アクティブ状態のいくつかの単純な状態管理、およびドロップされたファイルの処理を処理する必要があります。基本的にはそれだけです。
状態管理には、ReactuseStateフックを使用します。次に、useEffectフックを使用して、イベントリスナーをアタッチし、ドロップゾーンの状態を監視します。最後に、我々はまたしますmemoize使用してすべてのコンポーネントをメモHOCを。構築を始めましょう。
最初に必要なのは、ファイルのドロップゾーンコンポーネントを定義することです。これには、そのprops
、またはコンポーネントAPIのインターフェイスの定義も含まれます。ドロップゾーンコンポーネントは、6つのイベントハンドラーを受け入れます。これらのハンドラのうちの4つは、以下のようなイベントに呼び出されますdragenter
、dragleave
、dragover
とdrop
。
これらのハンドラーにより、このドロップゾーンコンポーネントを使用するすべてのユーザーが、これらのイベントが発生したときにコードを実行できるようになります。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
イベントです。このイベントは、ファイルがドロップゾーンに入るとトリガーされ、誰かがファイルを取得してドロップゾーンに配置します。このイベントを使用して、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
イベントの処理も非常に簡単になります。このイベントは、一部のファイルがドロップゾーンを離れたときに発生し、その上にホバーしなくなったときに発生します。このイベントを処理するには、いくつかのことを行う必要があります。まず、デフォルトのイベントと伝播を再度防止します。
次に行うことは、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'
あります!カスタムファイルのドロップゾーンコンポーネントを作成しました。これはスタンドアロンコンポーネントであるため、必要な場所で使用できます。このチュートリアルを楽しんでいただけたでしょうか。また、このチュートリアルが、新しくて便利なことを学ぶのに役立つことを願っています。
この記事が気に入ったら、今後の投稿を見逃さないように購読してください。