아래 코드는 이벤트 처리기에서 file 값을 얻고있다.
export default function FileInput(){
const onChange = (e:ChangeEvent<HTMLInputElement>) =>{
const files:FileList | null = e.target.files
}
return <input type="file" onChange={onChange} multiple accept="image/*">
}
는 File 타입의 리스트이며 유사 배열 객체이다. 유사 배열 객체는 Array.from() 함수로 배열로 변환 할수 있다.
const fileArray:File[] = Array.from(files)
File 타입 객체를 읽을 수 있도록 자바크스립트 엔진은 FileReader 라는 클래스를 기본 제공. readAsDataUrl() 메서드 제공.
const file:File
const fileReader = new FileReader
fileReader.readAsDataUrl(file)
readAsDataUrl() 메서드는 File 타입 객체를 읽어서 문자열로된 이미지를 제공, 이런 방식을 base64 인코딩 이라고 함.
File 타입 객체에 담긴 데이터는 바이너리 데이터이므로 base64 인코딩을 진행할때 시간이 걸림, FileReader 는 onload 이벤트 속성을 제공.
const fileReader = new FileReader()
fileReader.onload = (e:ProgressEvent<FileReader>) =>{
//javascript가 기본으로 제공하는 타입.
if(e.target){
const result = e.target.result //base64 인코딩결과
}
}
fileReader.readAsDataURL(file)
export const imageFileReaderP = (file:Blob) =>
//Blob(Binary Large Object) 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용
new Promise<string>((resolve,rejects) => {
const fileReader = new FileReader()
fileReader.onload = (e:ProgressEvent<FileReader>) => {
const result = e.target?.result
//이벤트 타켓을 값으로 가져옴. 이미지파일을 제대로 가져왔으면 resolve, 아니면 rejects
if(result && typeof result === "string") resolve(result)
else rejects(new Error("imageFileReaderP: can't read image file "))
}
fileReader.readAsDataURL(file)
})
onDragOver,onDrop
preventDefault()
preventDefault() 메서드는 이벤트가 발생했을 때 이 이벤트와 관련된 웹 브라우저의 기본 구현 내용을 실행하지 않게 함.
웹 브라우저는 기본으로 drop 이벤트가 발생하지 않도록 설계 되어있어 drop 이벤트가 발생하려면 dragover 이벤트 처리기에서 preventDefalut() 메서드를 호출해야 함.
파일을 ondrop 했을 때 브라우저는 파일을 새로운 창을 열어 보여주기 때문에 preventDefault()를 해주는 것이 좋음.
✩ DragEvent 에서 가장 중요한 속성은
dataTransfer
- 파일을 드롭했을때 files 속성으로 드롭한 파일의 정보를 알 수 있음.
구현할 이벤트 처리기
1. onClick(눌렀을때 파일추가창열기), 2. onDragOver,onDrop(파일 드롭영역), 3. onChange(파일드랍,파일추가했을시)
onClick 이벤트 처리기
const inputRef = useRef<HTMLInputElemnt>(null)
//click(),blur(),focus() 리엑트 요소가 가상 DOM 일때는 호출할수 없음,
//ref 속성값으로 얻은 DOM객체를 이용해 호출.
const onDivClick = useCallback(()=> inputRef.current?.click(),[]
input file 타입에 열기 대화상자는 click() 으로 인해 발생. click() 는 물리DOM 상태에서만 가능.
여러개 파일을 처리할 함수.
//여러개 이미지 파일을 문자열로 저장.
const [imageUrl,setImageUrl] = useState<string[]>([])
//files 를 배열로 가져올것임(Array.from())
const makeImageUrl = useCallback((files:File[])=>{
const promises = files.map(imageFileReaderP)
//promises 결과값을 then 콜백 함수에 넘겨줌.
Promise.all(promises)
//세터함수를 호출해 imageUrl에 새로운 값을 입력.
.then(urls => setImageUrl(imageUrl => [...urls,...imageUrl])
},[])
const onInputChange = useCallback((e:ChangeEvent<HTMLInputElement>)=>{
const files = e.target.files
files && makeImageUrl(Array.from(files))
},[])
//DragOver
const divDragOver = useCallback((e: DragEvent) => {
e.preventDefault()
}, [])
//onDrop
const divOnDrop = useCallback(
(e: DragEvent) => {
e.preventDefault()
//file 속성 드랍했을때 파일 정보 확인
const files = e.dataTransfer?.files
files && makeImageUrls(Array.from(files))
},
[makeImageUrls]
)
useMemo() 로 마무리
{}로 몸통을 감싸면 useMemo() 메서드가 캐싱하지 못함. 주의.
//image적용
const images = useMemo(
() =>
imageUrl.map((url, index) => (
//div 컴포넌트를 만들어서 사용함.
<Div
key={index}
src={url}
className="p-2 m-2 bg-transparent bg-center bg-no-repeat bg-contain"
width="5rem"
height="5rem"
/>
)),
[imageUrl]
)
div 컴포넌트
import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react'
type RectDivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
type divProps = RectDivProps & {
src?: string
width?: string
height?: string
}
export const Div: FC<divProps> = ({
className: _className,
src,
style: _style,
width,
height,
...props
}) => {
const style = {
..._style,
backgroundImage: src && `url(${src})`,
width,
height
}
const className = ['box-sizing', src, _className].join('')
return <div {...props} className={className} style={style} />
}