ReactでHTMLドラッグアンドドロップAPIを使用する方法

公開: 2022-03-10
クイックサマリー↬このチュートリアルでは、ファイルと画像をアップロードするためのReactドラッグアンドドロップコンポーネントを作成します。 その過程で、HTMLのドラッグアンドドロップAPIについて学習します。 また、useReducerフックを使用してReact機能コンポーネントの状態を管理する方法についても学習します。

ドラッグアンドドロップAPIは、HTMLの最も優れた機能の1つです。 これは、Webブラウザにドラッグアンドドロップ機能を実装するのに役立ちます。

現在のコンテキストでは、ブラウザの外部からファイルをドラッグします。 ファイルをドロップすると、それらをリストに入れて名前を表示します。 ファイルが手元にあれば、クラウドサーバーにアップロードするなど、ファイルに対して他の操作を実行できます。

このチュートリアルでは、Reactアプリケーションでドラッグアンドドロップのアクションを実装する方法に焦点を当てます。 必要なのがプレーンなJavaScript実装である場合は、最初に「Vanilla JavaScriptを使用してドラッグアンドドロップファイルアップローダーを作成する方法」を読みたいと思うでしょう。これは、JosephZimmermanが少し前に書いた優れたチュートリアルです。

dragenterdragleavedragover 、およびdropイベント

8つの異なるドラッグアンドドロップイベントがあります。 それぞれがドラッグアンドドロップ操作の異なる段階で起動します。 このチュートリアルでは、アイテムがドロップゾーンにドロップされたときに発生する4つ( dragenterdragleavedragoverdrop )に焦点を当てます。

  1. ドラッグされたアイテムが有効なドロップターゲットに入ると、 dragenterイベントが発生します。
  2. ドラッグされたアイテムが有効なドロップターゲットを離れると、 dragleaveイベントが発生します。
  3. ドラッグdragoverイベントは、ドラッグされたアイテムが有効なドロップターゲット上にドラッグされているときに発生します。 (数百ミリ秒ごとに起動します。)
  4. dropイベントは、アイテムが有効なドロップターゲットにドロップしたとき、つまりドラッグしてリリースしたときに発生します。

ondragoverおよびondropイベントハンドラー属性を定義することにより、任意のHTML要素を有効なドロップターゲットに変えることができます。

MDN Webドキュメントから、8つのイベントに関するすべてを学ぶことができます。

ジャンプした後もっと! 以下を読み続けてください↓

Reactのドラッグアンドドロップイベント

開始するには、次のURLからチュートリアルリポジトリのクローンを作成します。

 https://github.com/chidimo/react-dnd.git

01-startブランチをチェックしてください。 yarnも取り付けられていることを確認してください。 あなたはyarnpkg.comからそれを得ることができます。

ただし、必要に応じて、新しいReactプロジェクトを作成し、 App.jsのコンテンツを次のコードに置き換えます。

 import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;

また、 App.cssのコンテンツを以下のCSSスタイルに置き換えます。

 .App { margin: 2rem; text-align: center; } h1 { color: #07F; } .drag-drop-zone { padding: 2rem; text-align: center; background: #07F; border-radius: 0.5rem; box-shadow: 5px 5px 10px #C0C0C0; } .drag-drop-zone p { color: #FFF; } .drag-drop-zone.inside-drag-area { opacity: 0.7; } .dropped-files li { color: #07F; padding: 3px; text-align: left; font-weight: bold; }

リポジトリのクローンを作成した場合は、次のコマンドを(順番に)発行してアプリを起動します。

 yarn # install dependencies yarn start # start the app

次のステップは、ドラッグアンドドロップコンポーネントを作成することです。 src/フォルダー内にDragAndDrop.jsファイルを作成します。 ファイル内に次の関数を入力します。

 import React from 'react'; const DragAndDrop = props => { const handleDragEnter = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragLeave = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragOver = e => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = e => { e.preventDefault(); e.stopPropagation(); }; return ( <div className={'drag-drop-zone'} onDrop={e => handleDrop(e)} onDragOver={e => handleDragOver(e)} onDragEnter={e => handleDragEnter(e)} onDragLeave={e => handleDragLeave(e)} > <p>Drag files here to upload</p> </div> ); }; export default DragAndDrop;

return divで、フォーカスHTMLイベントハンドラー属性を定義しました。 純粋なHTMLとの唯一の違いは、キャメルケースであることがわかります。

onDragOverおよびonDropイベントハンドラー属性を定義したため、 divは有効なドロップターゲットになりました。

これらのイベントを処理する関数も定義しました。 これらの各ハンドラー関数は、引数としてイベントオブジェクトを受け取ります。

イベントハンドラーごとに、 preventDefault()を呼び出して、ブラウザーがデフォルトの動作を実行しないようにします。 デフォルトのブラウザの動作は、ドロップされたファイルを開くことです。 また、 stopPropagation()を呼び出して、イベントが子要素から親要素に伝播されないようにします。

DragAndDropコンポーネントをAppコンポーネントにインポートし、見出しの下にレンダリングします。

 <div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>

次に、ブラウザでコンポーネントを表示すると、次の画像のようなものが表示されます。

ドロップゾーン
ドロップゾーンに変換されるdiv (大プレビュー)

リポジトリをフォローしている場合、対応するブランチは02-start-dragndrop

useReducerフックを使用した状態の管理

次のステップは、各イベントハンドラーのロジックを作成することです。 その前に、ドロップされたファイルを追跡する方法を検討する必要があります。 ここから、状態管理について考え始めます。

ドラッグアンドドロップ操作中は、次の状態を追跡します。

  1. dropDepth
    これは整数になります。 これを使用して、ドロップゾーンの深さを追跡します。 後で、これをイラストで説明します。 (私のためにこれに光を当ててくれたEgor Egorovの功績です!
  2. inDropZone
    これはブール値になります。 これを使用して、ドロップゾーン内にいるかどうかを追跡します。
  3. FileList
    これがリストになります。 ドロップゾーンにドロップされたファイルを追跡するために使用します。

状態を処理するために、ReactはuseStateフックとuseReducerフックを提供します。 状態が前の状態に依存する状況を処理することを考慮して、 useReducerフックを選択します。

useReducerフックは、タイプ(state, action) => newStateのレデューサーを受け入れ、 dispatchメソッドとペアになっている現在の状態を返します。

useReducer詳細については、Reactのドキュメントをご覧ください。

Appコンポーネント内( returnステートメントの前)に、次のコードを追加します。

 ... const reducer = (state, action) => { switch (action.type) { case 'SET_DROP_DEPTH': return { ...state, dropDepth: action.dropDepth } case 'SET_IN_DROP_ZONE': return { ...state, inDropZone: action.inDropZone }; case 'ADD_FILE_TO_LIST': return { ...state, fileList: state.fileList.concat(action.files) }; default: return state; } }; const [data, dispatch] = React.useReducer( reducer, { dropDepth: 0, inDropZone: false, fileList: [] } ) ...

useReducerフックは、レデューサーと初期状態の2つの引数を受け入れます。 現在の状態と、状態を更新するためのdispatch関数を返します。 状態は、 typeとオプションのペイロードを含むアクションをディスパッチすることによって更新されます。 コンポーネントの状態に対して行われる更新は、アクションタイプの結果としてcaseステートメントから返される内容に依存します。 (ここで、初期状態はobjectであることに注意してください。)

状態変数ごとに、対応するcaseステートメントを定義して更新しました。 更新は、 useReducerによって返されるdispatch関数を呼び出すことによって実行されます。

次に、 dataを渡し、 App.jsファイルにあるDragAndDropコンポーネントにpropsとしてdispatchします。

 <DragAndDrop data={data} dispatch={dispatch} />

DragAndDropコンポーネントの上部で、 propsから両方の値にアクセスできます。

 const { data, dispatch } = props;

リポジトリをフォローしている場合、対応するブランチは03-define-reducersです。

イベントハンドラーのロジックを完成させましょう。 省略記号は2つの線を表すことに注意してください。

 e.preventDefault() e.stopPropagation() const handleDragEnter = e => { ... dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth + 1 }); }; const handleDragLeave = e => { ... dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth - 1 }); if (data.dropDepth > 0) return dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false }) };

次の図では、ドロップゾーンAとBがネストされています。Aが対象のゾーンです。 これは、ドラッグアンドドロップイベントをリッスンする場所です。

ondragenterおよびondragleaveイベントの図
ondragenterおよびondragleaveイベントの図(大プレビュー)

ドロップゾーンにドラッグすると、境界に到達するたびに、 ondragenterイベントが発生します。 これは、境界A-inB-in発生します。 ゾーンに入っているので、 dropDepthをインクリメントします。

同様に、ドロップゾーンからドラッグすると、境界に到達するたびに、 ondragleaveイベントが発生します。 これは、境界A-outB-outで発生します。 ゾーンを離れるので、 dropDepthの値をデクリメントします。 境界B-outinDropZonefalseに設定していないことに注意してください。 そのため、dropDepthをチェックし、 0より大きい関数dropDepthから戻るためのこの行があります。

 if (data.dropDepth > 0) return

これは、 ondragleaveイベントが発生した場合でも、ゾーンA内にいるためですA-outをヒットした後でのみ、 dropDepth0になり、 inDropZonefalseに設定します。 この時点で、すべてのドロップゾーンを残しました。

 const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };

このイベントが発生するたびに、 inDropZonetrueに設定します。 これは、私たちがドロップゾーン内にいることを示しています。 また、 dataTransferオブジェクトのdropEffectcopyに設定します。 Macでは、これは、ドロップゾーンでアイテムをドラッグするときに緑色のプラス記号を表示する効果があります。

 const handleDrop = e => { ... let files = [...e.dataTransfer.files]; if (files && files.length > 0) { const existingFiles = data.fileList.map(f => f.name) files = files.filter(f => !existingFiles.includes(f.name)) dispatch({ type: 'ADD_FILE_TO_LIST', files }); e.dataTransfer.clearData(); dispatch({ type: 'SET_DROP_DEPTH', dropDepth: 0 }); dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false }); } };

ドロップされたファイルには、 e.dataTransfer.filesを使用してアクセスできます。 値は配列のようなオブジェクトであるため、配列拡散構文を使用してJavaScript配列に変換します。

ここで、ファイルの配列に追加する前に、少なくとも1つのファイルがあるかどうかを確認する必要があります。 また、 fileListにすでに存在するファイルを含めないようにします。 dataTransferオブジェクトは、次のドラッグアンドドロップ操作の準備としてクリアされます。 また、 dropDepthinDropZoneの値をリセットします。

DragAndDropコンポーネントのdivclassNameを更新します。 これにより、 data.inDropZoneの値に応じて、 divclassNameが条件付きで変更されます。

 <div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>

data.fileListを介してマッピングすることにより、 App.js内のファイルのリストをレンダリングします。

 <div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop data={data} dispatch={dispatch} /> <ol className="dropped-files"> {data.fileList.map(f => { return ( <li key={f.name}>{f.name}</li> ) })} </ol> </div>

次に、いくつかのファイルをドロップゾーンにドラッグアンドドロップしてみます。 ドロップゾーンに入ると、 inside-drag-areaクラスがアクティブになっているため、背景の不透明度が低くなっていることがわかります。

ドロップゾーン内でファイルを解放すると、ドロップゾーンの下にファイル名が表示されます。

ドラッグオーバー中に不透明度が低いドロップゾーン
ドラッグオーバー中に不透明度が低いドロップゾーン(大きなプレビュー)
ドロップゾーンにドロップされたファイルのリスト
ドロップゾーンにドロップされたファイルのリスト(大プレビュー)

このチュートリアルの完全版は、 04-finish-handlersブランチにあります。

結論

HTMLドラッグアンドドロップAPIを使用してReactでファイルのアップロードを処理する方法を見てきました。 また、 useReducerフックを使用して状態を管理する方法も学びました。 ファイルhandleDrop関数を拡張できます。 たとえば、必要に応じて、ファイルサイズを制限するための別のチェックを追加できます。 これは、既存のファイルのチェックの前または後に発生する可能性があります。 ドラッグアンドドロップ機能に影響を与えることなく、ドロップゾーンをクリック可能にすることもできます。

資力

  • 「フックAPIリファレンス: useReducer 」、React Docs
  • 「HTMLドラッグアンドドロップAPI」、MDNWebドキュメント
  • 「DOMを使用したWebおよびXML開発の例」、MDN Web docs
  • 「VanillaJavaScriptを使用してドラッグアンドドロップファイルアップローダーを作成する方法」、Smashing Magazine、Joseph Zimmerman
  • 「Reactでの単純なドラッグアンドドロップファイルアップロード」、Egor Egorov、Medium