하루에 하나씩
[02] 이벤트 속성 이해하기 본문
모든 HTML요소는 onmouseenter나 onmouseover처럼 'on'으로 시작하는 속성을 제공
- 이를 이벤트 속성이라고 한다.
이벤트 속성을 만들 파일 작성하기
1. src 디렉터리 안에, copy 디렉터리를 만들고 CopyMe.tsx파일을 만들기
2. 복사용 기본 코드 작성
export default function CopyMe(){
return <div>CopyMe</div>
}
3. src 디렉터리에 page 디렉터리 생성 후 파일을 복사, 각 이벤트를 넣기
4. src/App 파일에 해당 코드 작성하기 (방금 작성한 11개의 컴포넌트 사용)
import DispatchEvent from './pages/DispatchEvent';
import DragDrop from './pages/DragDrop';
import EventBubbling from './pages/EventBubbling';
import EventListener from './pages/EventListener';
import FileDrop from './pages/FileDrop';
import FileInput from './pages/FileInput';
import OnChange from './pages/OnChange';
import OnClick from './pages/OnClick'
import ReactOnClick from './pages/ReactOnClick';
import StopPropagation from './pages/StopPropagation';
import VariousInput from './pages/VariousInput';
export default function App(){
return(
<div>
<FileDrop />
<DragDrop />
<FileInput />
<OnChange />
<VariousInput />
<StopPropagation />
<EventBubbling />
<DispatchEvent />
<ReactOnClick />
<OnClick />
<EventListener />
</div>
)
}
5. 실행...
이벤트
- 화면 UI를 다루는 모든 프레임워크는 사용자가 화면 UI에서 버튼을 누르거나 입력 등의 행위가 발생하면
- UI를 다루는 코드에 알려줘야하는데, 이를 이벤트가 발생했다고 함
이벤트 타입
종류 | 설명 |
type | 이벤트 이름으로 대소문자 구분 X |
isTrusted | 이벤트가 웹 브라우저에서 발생한것인지(true) 프로그래밍에서 발생한 것인지 (false) 구분 |
target | 이벤트가 처음 발생한 HTML 요소 |
currentTarget | 이벤트의 현재 대상 이벤트 버블링 중에서 이벤트가 현재 위치한 객체 |
bubbles | 이벤트가 DOM을 타고 버블링될지 여부 결정 |
예시 : click인 이름을 가진 Event 객체를 생성하는 코드
new Event('click', { bubbles : true })
EventTarget 타입
- 모든 HTML요소는 HTMLElement 상속타입을 가짐
- 최상위 EventTarget을 시작으로, Node, Element 순으로 상속
- 브라우저 객체 모델인 window도 EventTarget에서 상속하는 타입임
이벤트 처리기
EventTarget가 제공하는 3가지 메서드
1. addEventListner
2. removeEventListener
3. dispatchEvent
- 셋 모두 이벤트 처리기의 일종으로, 이벤트를 귀 기울여 듣는 다는 의미(event Listener)
- 하나의 이벤트에 여러 이벤트 처리기 부착 가능
1. addEventListner
DOM_객체.addEventListener(이벤트 이름 : string, 콜백함수: (e: Event) => void)
- window 객체의 경우 EventTarget 타입을 상속하므로 addEventListener 메서드를 제공함
- 리액트 프로젝트는 항상 public 디렉터리의 index.html파일에 <div id="root">태그가 포함 되어 있으므로, 다음과 같이 코드 작성이 가능함
document.getElementById('root')?.addEventListener('click', (e: Evet) => {
const {isTrusted, target, bubbles } = e
console.log('mouse click occurs', isTrusted, target, bubbles)
})
- 옵셔널 체이닝이 사용되었음 (?.)
- 이는 getElementById가 null을 반환할 수도 있기 때문에, 이를 방지하기 위해 사용
EventListener.tsx 파일에 다음과 같이 코드 작성
document.getElementById('root')?.addEventListener('click', (e: Event) =>{
const {isTrusted, target, bubbles} = e;
console.log('mouse click occurs.', isTrusted, target, bubbles);
})
document.getElementById('root')?.addEventListener('click', (e: Event) =>{
const {isTrusted, target, bubbles} = e;
console.log('mouse click also occurs.', isTrusted, target, bubbles);
})
export default function CopyMe(){
return <div>EventListener</div>
}
- 1행, 5행에서 root에 대한 click이벤트 처리기가 붙어있음
- 해당 코드를 실행하면
물리 DOM 객체의 이벤트 속성
- addEventListener 사용법이 조금 번거로움
- 이 때문에 window나 대부분의 HTML요소는, onclick 처럼 'on' 뒤에 이벤트 이름을 붙인 속성을 제공
- 이벤트 속성값에는 항상 이벤트 처리기를 설정해야 함
- 아까 했던 addEventListener을 onclick으로 바꾸면 다음과 같음
window.onclick = (e:Event) => console.log('mouse click occurs')
- 또한, <div id='root'> 에서 DOM 객체의 onclick 속성값을 다음과 같이 구현 가능
- 옵셔널 체이닝 연산자는 document.getElementById('root')?.onclick = 콜백함수 처럼 값 설정 구문에서는 사용 불가능
- 따라서 다음과 같이 구현
const rootDiv = document.getElementById('root')
if (rootDiv){
rootDiv.onclick = (e: Event) => console.log('mouse click occurs')
}
src/pages 디렉터리에서 Onclick.tsx 파일 열고 다음과 같이 작성
const rootDiv = document.getElementById('root')
if (rootDiv){
rootDiv.onclick = (e: Event) => {
const {isTrusted, target, bubbles} = e;
console.log('mouse click occurs on rootDiv', isTrusted, bubbles)
}
rootDiv.onclick = (e: Event) => {
const {isTrusted, target, bubbles} = e;
//prettier-ignore
console.log('mouse click also occurs on rootDiv', isTrusted, bubbles)
}
}
export default function Onclick(){
return <div>OnClick</div>
}
- addEventListener와 다르게, onclick은 마지막에 설정한 콜백 함수를 호출하기 때문에 하나만 호출됨
리액트 프레임워크의 이벤트 속성
- 리액트도 on이벤트 식으로 작성된 HTML요소의 이벤트 속성 제공
- 차이점이 있다면, 카멜케이스(onClick...)로 사용하는 것
- 리액트 컴포넌트에서 이벤트 속성에 설정하는 콜백 함수는 매개변수 e의 타입이 Event가 아닌 SyntheticEvent를 설정해야 함
- Synthetic이라는 단어는 '모든 종류의 이벤트를 종합한' 뜻으로 해석
- BaseSyntheticEvent를 상속
Base SyntheticEvent 주요내용
interface BaseSyntheticEvent<E = object, C = any, T = any>{
nativeEvent: E;
currentTarget: C;
target: T;
preventDefault(): void;
stopPropagation(): void;
}
리액트 물리DOM에서 일어나는 이벤트를 '네이티브 이벤트'라고 함
- BaseSyntheticEvent의 nativeEvent속성은 물리DOM에서 발생하는 Event의 세부 타입인 PointerEvent와 같은 이벤트 객체 저장에 사용
- currentTarget 속성은 이벤트 버블링과정에서 현재 이벤트를 수신한 DOM 객체를 알고 싶을 때 사용
- target 속성은 이벤트를 처음 발생시긴 DOM 객체를 알고 싶을 때 사용
src/pages의 ReactOnClick.tsx파일을 다음과 같이 수정
import { SyntheticEvent } from "react"
export default function ReactOnClick() {
const onClick = (e: SyntheticEvent) => {
const { isTrusted, target, bubbles } = e;
console.log('mouse click occurs on <button>', isTrusted, target, bubbles)
}
return (
<div>
<p>ReactOnclick</p>
<button onClick={onClick}>Click Me</button>
</div>
)
}
EventTarget의 dispatchEvent 메서드
dispatchEvent(event: Event): boolean;
Event 객체는 다음처럼 만들 수 있음
new Event('click', {bubbles: true})
이렇게 생성된 Event 타입의 객체는 다음처럼 Target 속성값이 되는 DOM 객체의 dispatchEvent 메서드를 통해 이벤트 발생 가능
DOM.dispatchEvent(new Event('click', {bubbles: true}))
모든 DOM 객체의 부모 타입인 HTMLElement는 click 메서드를 제공한다.
- distpatchEvent 코드와 완전히 똑같이 동작
- DOM.click(); ...
DistpatchEvent 파일을 다음과 같이 수정
export default function DispatchEvent(){
const onCallDispatchEvent = () => {
console.log('onCallDispatchEvent')
document.getElementById('root')?.dispatchEvent(new Event('click', {bubbles: true}))
}
const onCallClick = () => {
console.log('onCallClick')
document.getElementById('root')?.click()
}
return(
<div>
<p>DispatchEvent</p>
<button onClick={onCallDispatchEvent}>call dispatchEvent</button>
<button onClick={onCallClick}>call click</button>
</div>
)
}
- 이 때, dispatchEvent와 click메서드로 발생한 이벤트가 false인 것을 알 수 있다.
- isTrusted가 웹 브라우저에서 발생한건지, 프로그래밍에서 발생한건지 확인한 것이므로
- 저 둘은 프로그래밍에서 발생한 이벤트인 것을 알 수 있음
이벤트 버블링
- 자식 요소에서 발생한 이벤트가 가까운 부모 요소에서 가장 먼 부모 요소까지 전달이 계속 되는 현상
- 이벤트가 지속적으로 호출, 버블링으로 상위 태그 까지 이벤트가 전달이됨
- 이 때, e.currentTarget의 경우 각각의 DOM 객체가 설정이 됨
EventBubblig에 다음과 같이 코드 작성
import type { SyntheticEvent } from "react";
export default function EventBubbling(){
const onDivClick = (e: SyntheticEvent) => {
const {isTrusted, target, bubbles, currentTarget} = e;
console.log('click event bubbles on <div>', isTrusted, target, bubbles, currentTarget);
}
const onButtonClick = (e: SyntheticEvent) => {
const {isTrusted, target, bubbles} = e;
console.log('click event starts at <button>', isTrusted, target, bubbles)
}
return(
<div onClick={onDivClick}>
<p>EventBubbling</p>
<button onClick={onButtonClick}> Click Me </button>
</div>
);
}
- 화면을 보면, button에서 발생한 이벤트가 부모 요소인 onDivClick 까지 전달된 것을 볼 수 있음
- 값을 보면 currentTarget값도 다름 (버블링 중 현재 위치한 객체를 나타내므로)
StopPropagation 메서드와 이벤트 전파 막기
- 버블링이 큰 문제는 아니나, 중단이 필요한 경우도 있음
- 이 때는 StopPropagation 메서드를 사용
StopPropagation.tsx에 다음과 같이 코드 작성
import type { SyntheticEvent } from "react"
export default function StopPropagation(){
const onDivClick = (e: SyntheticEvent) => console.log('click event bubbles on <div>')
const onButtonClick = (e: SyntheticEvent) => {
console.log('mouse click occurs on <button> and call stopPropagation')
e.stopPropagation()
}
return(
<div onClick={onDivClick}>
<p>Propagation</p>
<button onClick={onButtonClick}>Click Me and event Stop propagation</button>
</div>
)
}
- 아까와는 다르게 div태그에는 이벤트가 전달되지 않았음.
Input 요소의 이벤트 처리
- Input은 type 속성값에 따라 화면에 나타나는 모습과, 사용자 입력을 받는법이 다름
VariousInputs 파일에 다음과 같이 작성
export default function VariousInputs(){
return(
<div>
<p>VariousInputs</p>
<div>
<input type="text" placeholder="enter some texts"/>
<input type="password" placeholder="enter your password"/>
<input type="email" placeholder="enter email address"/>
<input type="range"/>
<input type="button" value="I'm button"/>
<input type="checkbox" value="I'm a checkbox" defaultChecked />
<input type="radio" value="I'm a radio" defaultChecked />
<input type="file" />
</div>
</div>
)
}
Input의 onChange 이벤트 속성
- Input의 type이 text일 때 발생하는 이벤트
import type {ChangeEvet} from 'react';
export default function OnChange(){
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log('onChange', e.target.value)
}
return <input type="text" onChange={onChange} />
}
- 이벤트 속성에 onChange라는 이름의 이벤트 처리기 설정
- 이 때 매개변수의 타입은 ChangeEvent<HTMLInputElement>로 설정
- 이러면 HTMLInputElement 타입의 물리 DOM 객체 값을 e.target 형태로 획득 가능
Input 요소의 이벤트 관련 속성들
input : React.DetaildHTMLProps<React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement>;
- 위 코드로, Input 요소가 제공하는 속성은 React.InputHTMLAttributes<HTMLInputElement> 형태로 얻어
InputHTMLAttributes<T> 속성에서 onChange 이벤트와 관련된 속성
- Check & Radio
- checked 속성
- text, email, password, range
- value 속성
- file
- files 속성 값
- 이 속성들을 통해 사용자가 입력한 구체적인 내용을 알 수 있음
Input의 defaultValue와 defaultChecked 속성
- check : 사용자가 input에 입력한 값을 얻을 때 사용
- defaultValut와 defaultChecked : 초깃값 설정에 사용
OnChange 컴포넌트 구현하기
import type { ChangeEvent, SyntheticEvent } from "react"
export default function OnChange(){
const onChangeValue = (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation()
e.preventDefault()
console.log('onChangeValue', e.target.value)
}
const onChangeChecked = (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation()
console.log('onChangeChecked', e.target.checked)
}
const onChangeFiles = (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation()
console.log('onChangeFiles', e.target.files)
}
return(
<div>
<p>onChange</p>
<input type="text" onChange={onChangeValue}
placeholder="type some text" defaultValue="Hello">
</input>
<input type="checkbox" onChange={onChangeChecked} defaultChecked />
<input type="file" onChange={onChangeFiles} multiple accept="images/*"/>
</div>
)
}
multiple과 accept
- multiple의 경우 파일을 여러개 선택할 수 있도록 하고
- accept의 경우 파일의 확장자를 제한함
input type="file" 에서의 onChange
- e.target.files 속성값으로 사용자가 선택한 파일 목록을 얻을 수 있음
- 이 때 속성의 타입은 FileList로, 리액트가 아니라 웹 브라우저의 자바스크립트 엔진이 제공
- FileList의 item과 인덱스 연산자 []는 해당하는 item 인덱스에 따라 File 타입의 속성 값 얻기 가능
- Blob 타입과 Blob 타입을 확장한 File 타입도 제공
FileInput 을 다음과 같이 작성
import type { ChangeEvent, SyntheticEvent } from "react"
export default function FileInput(){
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const files: FileList | null = e.target.files
if(files){
for(let i = 0; i < files.length; i++){
const file: File | null = files.item(i) //or file = files[i];
console.log(`file[${i}]`, file)
}
}
}
return(
<div>
<p>File Input</p>
<input type="file" onChange={onChange} multiple accept="image/*" />
</div>
)
}
- 콘솔 창에서 해당 이미지의 세부 내용을 볼 수 있음
Dage & Drop 이벤트 처리
- 모든 HTMLElement 상속 요소는 draggable 이라는 boolean 타입의 속성을 제공
- true 이면 드래그 가능 아니면 불가능
Drag & Event
종류 | 발생 시기 | 리액트 이벤트 속성 이름 |
|||
dragenter | 드래그한 요소나 텍스트 블록을 적합한 드롭 대상 위에 올렸을 때 | onDragEnter | |||
dragstart | 사용자가 요소나 텍스트 블록을 드래그 하기 시작했을 때 | onDragStart | |||
drag | 요소나 텍스트 블록을 드래그할 때 | onDrag | |||
dragover | 요소나 택스트 블록을 적합한 드롭 대상 위로 지나갈 때 | onDragOver | |||
dragleave | 드래그하는 요소나 텍스트 블록이 적합한 드롭 대상 위를 벗어났을 때 | onDragLeave | |||
dragend | 드래그가 끝났을 때 | onDragEnd | |||
drop | 요소나 텍스트 블록을 적합한 드롭 대상에 드롭했을 때 | onDrop |
- DragEvent 타입에서 dataTransfer이라는 속성이 있음
- 이 속성은 DataTransfer 타입을 가짐
- 파일이 드롭 되었을 땐 files 속성으로 드롭한 파일의 정보를 알 수 있음
- 나머지는 각자에 맞게 알 수 있음
PreventDefault
- 어떤 사용자 액션에 따라 이벤트가 발생했을 때, 이 이벤트와 관련한 웹 브라우저의 기본 구현을 막는 것
- 웹 브라우저는 기본으로 drop 이벤트가 발생하지 않도록 설계됨
- 따라서 실행하려고 하면, dragover 이벤트 처리기에서 preventDefault를 호출해야함
DragDrop 컴포넌트를 다음과 같이 작성
import type { DragEvent } from "react"
export default function DragDrop(){
const onDragStart = (e: DragEvent<HTMLElement>) =>
console.log('onDragStart', e.dataTransfer)
const onDragEnd = (e: DragEvent<HTMLElement>) =>
console.log('onDragEnd', e.dataTransfer)
const onDragOver = (e: DragEvent) => e.preventDefault()
const onDrop = (e: DragEvent) => {
e.preventDefault()
console.log('onDrop', e.dataTransfer)
}
return(
<div>
<p>DragDrop</p>
<div draggable onDragStart={onDragStart} onDragEnter={onDragEnd}>
<h1>Drag Me</h1>
</div>
<div onDrop={onDrop} onDragOver={onDragOver}>
<h1>Drop over Me</h1>
</div>
</div>
)
}
FileDrop 컴포넌트 구현하기
- File Input과 다른점
- 웹 브라우저 바깥에서 안쪽으로 떨어지므로 draggable 같은 요소 필요 없음
- onDrop이 호출되어야 하므로 onDragOver에 preventDefault 메서드 호출 필요
- FileInput은 e.target.files에서 파일 객체을 얻지만
- FileDrop은 e.Transfer.files에서 파일 객체를 얻음 (드랍한 파일을 가져와야 하니까)
import { DragEvent } from "react"
export default function FileDrop(){
const onDragOver = (e: DragEvent) => e.preventDefault()
const onDrop = (e: DragEvent) => {
e.preventDefault()
const files = e.dataTransfer.files
if(files){
for(let i = 0; i < files.length; i++){
const file: File | null = files.item(i)
console.log(`file[${i}]`, file)
}
}
}
return (
<div>
<p>FileDrop</p>
<div onDrop={onDrop} onDragOver={onDragOver}>
<h1>Drop Image Files over Me</h1>
</div>
</div>
)
}
- 파일에서 onDrop 이벤트가 처리되어 파일 정보가 출력이 됨
'React' 카테고리의 다른 글
[Phaser3] TypeScript + React에서이동 구현하기 (0) | 2024.09.24 |
---|---|
[React] React Snippet 컴포넌트 단축키 사용법 (1) | 2024.09.01 |
[02] key와 children 속성 이해하기 (0) | 2024.04.16 |
[02] 컴포넌트 이해하기 (0) | 2024.04.15 |
[02] JSX 구문 이해하기 (0) | 2024.04.12 |