React에서 HTML 드래그 앤 드롭 API를 사용하는 방법
게시 됨: 2022-03-10드래그 앤 드롭 API는 HTML의 가장 멋진 기능 중 하나입니다. 웹 브라우저에서 끌어서 놓기 기능을 구현하는 데 도움이 됩니다.
현재 컨텍스트에서는 브라우저 외부에서 파일을 드래그합니다. 파일을 드롭하면 목록에 넣고 이름을 표시합니다. 파일을 가지고 있으면 파일에 대해 다른 작업을 수행할 수 있습니다(예: 클라우드 서버에 업로드).
이 튜토리얼에서는 React 애플리케이션에서 드래그 앤 드롭 동작을 구현하는 방법에 중점을 둘 것입니다. 간단한 JavaScript
구현이 필요한 경우 먼저 Joseph Zimmerman이 작성한 훌륭한 자습서인 "바닐라 JavaScript로 끌어서 놓기 파일 업로더를 만드는 방법"을 읽고 싶을 것입니다.
dragenter
, dragleave
, dragover
및 drop
이벤트
8개의 서로 다른 끌어서 놓기 이벤트가 있습니다. 각각은 끌어서 놓기 작업의 다른 단계에서 실행됩니다. 이 튜토리얼에서는 항목을 드롭 영역에 놓을 때 발생하는 네 가지( dragenter
, dragleave
, dragover
및 drop
)에 중점을 둘 것입니다.
-
dragenter
이벤트는 드래그된 항목이 유효한 놓기 대상에 들어갈 때 발생합니다. -
dragleave
이벤트는 드래그된 항목이 유효한 놓기 대상을 벗어날 때 발생합니다. -
dragover
이벤트는 드래그된 항목이 유효한 놓기 대상 위로 드래그될 때 발생합니다. (수백 밀리초마다 실행됩니다.) -
drop
이벤트는 항목이 유효한 드롭 대상에 떨어질 때 발생합니다.
ondragover
및 ondrop
이벤트 핸들러 속성을 정의하여 모든 HTML 요소를 유효한 놓기 대상으로 전환할 수 있습니다.
MDN 웹 문서에서 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;
반환 div
에서 포커스 HTML
이벤트 핸들러 속성을 정의했습니다. 순수한 HTML
과의 유일한 차이점은 낙타 모양이라는 것을 알 수 있습니다.
onDragOver
및 onDrop
이벤트 핸들러 속성을 정의했기 때문에 div
는 이제 유효한 놓기 대상입니다.
또한 이러한 이벤트를 처리하는 함수를 정의했습니다. 이러한 각 처리기 함수는 이벤트 개체를 인수로 받습니다.
각 이벤트 핸들러에 대해 preventDefault()
를 호출하여 브라우저가 기본 동작을 실행하지 못하도록 합니다. 기본 브라우저 동작은 드롭된 파일을 여는 것입니다. 또한 이벤트가 자식 요소에서 부모 요소로 전파되지 않도록 stopPropagation()
을 호출합니다.
DragAndDrop
구성 요소를 App
구성 요소로 가져와 제목 아래에 렌더링합니다.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
이제 브라우저에서 구성 요소를 보면 아래 이미지와 같은 내용이 표시되어야 합니다.
repo를 팔로우하는 경우 해당 분기는 02-start-dragndrop
useReducer
Hook으로 상태 관리하기
다음 단계는 각 이벤트 핸들러에 대한 로직을 작성하는 것입니다. 그렇게 하기 전에 삭제된 파일을 추적하는 방법을 고려해야 합니다. 여기에서 상태 관리에 대해 생각하기 시작합니다.
끌어서 놓기 작업 중에 다음 상태를 추적합니다.
-
dropDepth
이것은 정수가 됩니다. 우리는 드롭 존에 얼마나 많은 레벨이 있는지 추적하는 데 사용할 것입니다. 나중에 그림으로 설명하겠습니다. ( 저를 위해 빛을 비춰준 Egor Egorov에게 감사드립니다! ) -
inDropZone
이것은 부울이 됩니다. 우리는 이것을 사용하여 드롭 존 안에 있는지 여부를 추적합니다. -
FileList
이것은 목록이 될 것입니다. 이를 사용하여 드롭 영역에 드롭된 파일을 추적합니다.
상태를 처리하기 위해 React는 useState
및 useReducer
후크를 제공합니다. 우리는 상태가 이전 상태에 의존하는 상황을 다룰 것이라는 점을 감안할 때 useReducer
후크를 선택할 것입니다.
useReducer
후크는 (state, action) => newState
유형의 감속기를 허용하고 dispatch
메서드와 쌍을 이루는 현재 상태를 반환합니다.
React 문서에서 useReducer
에 대해 자세히 읽을 수 있습니다 .
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
후크는 리듀서와 초기 상태라는 두 가지 인수를 허용합니다. 현재 상태와 상태를 업데이트할 dispatch
함수를 반환합니다. 상태는 type
과 선택적 페이로드가 포함된 작업을 전달하여 업데이트됩니다. 구성 요소 상태에 대한 업데이트는 작업 유형의 결과로 case 문에서 반환된 내용에 따라 다릅니다. (여기서 초기 상태는 object
라는 점에 유의하십시오.)
각 상태 변수에 대해 해당 케이스 문을 정의하여 업데이트했습니다. 업데이트는 useReducer
에서 반환된 dispatch
함수를 호출하여 수행됩니다.
이제 App.js 파일에 있는 DragAndDrop
구성 요소에 data
를 dispatch
하고 props
으로 전달합니다.
<DragAndDrop data={data} dispatch={dispatch} />
DragAndDrop
구성 요소의 상단에서 props
의 두 값에 모두 액세스할 수 있습니다.
const { data, dispatch } = props;
repo를 따르는 경우 해당 분기는 03-define-reducers
입니다.
이벤트 핸들러의 논리를 완성해 보겠습니다. 줄임표는 두 줄을 나타냅니다.
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
이벤트가 시작됩니다. 이것은 경계 A-in
및 B-in
발생합니다. 영역에 들어가고 있으므로 dropDepth
를 증가시킵니다.
마찬가지로, 드롭 영역 밖으로 드래그할 때 경계에 도달할 때마다 ondragleave
이벤트가 시작됩니다. 이것은 경계 A-out
및 B-out
에서 발생합니다. 영역을 떠나기 때문에 dropDepth
값을 줄입니다. 경계 B-out
에서 inDropZone
을 false
로 설정하지 않았음을 주목하십시오. 이것이 dropDepth를 확인하고 0
보다 큰 dropDepth
함수에서 반환하기 위해 이 라인이 있는 이유입니다.
if (data.dropDepth > 0) return
이는 ondragleave
이벤트가 발생하더라도 여전히 영역 A 안에 있기 때문입니다. inDropZone
을 false
로 설정한 것은 A-out
을 치고 dropDepth
가 이제 0
이 된 후에야 가능합니다. 이 시점에서 우리는 모든 드롭 영역을 떠났습니다.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
이 이벤트가 발생할 때마다 inDropZone
을 true
로 설정합니다. 이것은 우리가 드롭 존 안에 있음을 알려줍니다. 또한 dataTransfer
객체의 dropEffect
를 copy
로 설정합니다. 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
배열로 변환합니다.
이제 파일 배열에 파일을 추가하기 전에 파일이 하나 이상 있는지 확인해야 합니다. 또한 이미 fileList
에 있는 파일을 포함하지 않도록 합니다. dataTransfer
객체는 다음 드래그 앤 드롭 작업을 준비하기 위해 지워집니다. dropDepth
및 inDropZone
값도 재설정합니다.
DragAndDrop
구성 요소에서 div
의 className
을 업데이트합니다. 이것은 data.inDropZone
의 값에 따라 div
의 className
을 조건부로 변경합니다.
<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
기능을 확장할 수 있습니다. 예를 들어 원하는 경우 파일 크기를 제한하기 위해 다른 검사를 추가할 수 있습니다. 이것은 기존 파일을 확인하기 전이나 후에 올 수 있습니다. 드래그 앤 드롭 기능에 영향을 주지 않고 드롭 영역을 클릭 가능하게 만들 수도 있습니다.
자원
- "Hooks API 참조:
useReducer
", React Docs - "HTML 드래그 앤 드롭 API", MDN 웹 문서
- "DOM을 사용한 웹 및 XML 개발의 예", MDN 웹 문서
- "바닐라 자바스크립트로 드래그 앤 드롭 파일 업로더를 만드는 방법", Joseph Zimmerman, Smashing Magazine
- "React에서 간단한 드래그 앤 드롭 파일 업로드", Egor Egorov, Medium