| 장 | 제목 |
|---|---|
| 4장 | 이벤트 핸들링 |
| 5장 | ref.DOM 에 이름달기 |
우선 이 코드를 먼저 보고 갑시다!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Title</title>
</head>
<body>
<button onclick="alert('알러트입니다')">Click</button>
</body>
</html>기존의 HTML 에서 DOM 요소에 접근을 할 때는 위의 코드와 같이 " " 사이에 자바스크립트 코드를 넣거나
혹은 같은 맥락의 addEventListener 를 통해 이벤트 핸들링을 하곤 했습니다.
리액트 에서의 이벤트핸들링은 비슷하면서도 살짝 다른데요,, 밑의 코드를 보겠습니다!
위의 HTML 코드와 다른 점을 느끼셨나요?
크게 두 가지 다른 점을 느낄 수 있습니다.
import React, { useState } from "react";
const Greeting = () => {
const [message, setMessage] = useState("");
const onClickHi = () => {
setMessage("하이염");
};
const onClickBye = () => {
setMessage("빠이염");
};
return (
<div>
<button onClick={onClickHi}>Hi</button>
<button onClick={onClickBye}>Bye</button>
<h1>{message}</h1>
</div>
);
};
export default Greeting;-
camelCase
저번주에서도 다뤘던 내용이지만 리액트에서는 이벤트 이름, 자바스크립트에 이미 존재하는 단어들을 카멜 케이스로 작성합니다. -
{ }
기존HTML에서는" "에 자바스크립트 코드를 넣었지만리액트에서는 함수 형태의 객체를 전달합니다.
💢 주의사항 💢
리액트 에서는 DOM 요소에만 이벤트를 설정할 수 있습니다.
<ExComponent onClick={doSomething} />예를 들어 위와 같이 컴포넌트에 onClick 을 설정한다면 이는 이벤트가 아닌 그냥 onClick 이라는 이름의 props 를 전달하게 되는 것입니다.
import React from "react";
const WriteMessage = () => {
const handleChange = (event) => {
console.log(event.target.value);
};
return (
<input
type="text"
name="message"
placeholder="Write Something ... 📝"
onChange={handleChange}
/>
);
};
export default WriteMessage;이런 식으로 input 값의 value 를 콘솔에 찍어볼 수 있습니다.
onChange 의 파라미터인 event 객체는 리액트 의 Systhetic Event 로 우리가 기존에 알고 있던 네이티브 이벤트의 객체로 이루어져 있어 비슷하게 사용하면 됩니다.
하지만 Systhetic Event 가 네이티브 이벤트와 다른 점은 Event Polling 인데요, 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없습니다.
Event Polling 의 해결방법과 Synthetic Event 에 대한 설명은 이 곳에서 추가로 확인할 수 있습니다.
위의 코드에 state 를 이용하여 value 를 업데이트 시키고 이를 화면에 찍어보겠습니다.
state 값 을 지난주에 배운 세터함수 를 활용하여 변경시킬 수 있습니다.
세터함수 에 위에서 콘솔에 찍어봤던 event.target.value 를 넣어주면 되겠죠?
이후 input 의 value 에 state 값 을 전달해주고 아무 태그나 이용하여 state 값 을 찍어봅시다.
import React, { useState } from "react";
const WriteMessage = () => {
const [message, setMessage] = useState("");
const handleChange = (event) => {
setMessage(event.target.value);
};
return (
<>
<input
type="text"
name="message"
placeholder="Write Something ... 📝"
value={message}
onChange={handleChange}
/>
<h1>내가 쓴 메시지: {message}</h1>
</>
);
};
export default WriteMessage;버튼을 누르면 input 의 value 를 리셋시켜 보겠습니다.
세터함수 를 이용하여 빈 문자열을 넣어주면 됩니다.
반복되는 코드가 중복되는 부분은 생략하였습니다.
// 콜백함수 handleReset
const handleReset = () => {
setMessage("");
};
// 버튼을 클릭하면 handleReset 실행
<button onClick={handleReset}>초기화</button>;input 이 하나가 아니라 여러 개라면 어떻게 해야할까요?
const [writer, setWriter] = useState("");
const [message, setMessage] = useState("");와 같이 input 의 개수만큼 useState 를 활용해도 되지만 input 의 개수가 많아진다면
위에서 사용한 event 객체를 활용하는 방법이 더 효율적일 것입니다.
state 값 을 단일 값이 아닌 객체로 선언해봅시다.
const [inputs, setInputs] = useState({
writer: "",
message: "",
});이렇게 되면 inputs 에 writer 와 message 라는 key 를 가진 객체가 생성됩니다.
writer 와 message 를 자유롭게 쓰기 위해선 Destructing (비구조화 할당) 이 유용하겠죠?
const { writer, message } = inputs;이제 컴포넌트 내에서 자유롭게 writer 와 message 를 사용할 수 있습니다.
메시지 작성자인 writer field 를 하나 추가하고 리셋 함수인 handleReset 까지 수정해보겠습니다.
// handleReset()
const handleReset = () => {
setInputs({
writer: "",
message: "",
});
};
// writer input field
<input
type="text"
name="writer"
placeholder="What's your name ... 👩"
value={writer}
onChange={handleChange}
/>;이제 handleChange 함수만 수정해주면 되는데요, 선행되어야 하는 자바스크립트 지식이 있습니다.
-
Spread
지난주 발표내용에도 있었고 예빈님이 추가설명까지 해주셨기 때문에 자세한 내용은 생략합니다.
MDN 혹은 벨로퍼트님 자료를 참고해주세요! -
Object
객체 안에서 key 를[ ]로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용됩니다.
~~이게 뭐라고 엄청 헷갈렸는데 쉽게 말하자면 변수(상수) 값을 객체의 key 로 쓰고 싶다~ 하면 쓰시면 되는겁니다!~~const newKey = "name"; const object = { [newKey]: "kevin" }; // object = {name: 'kevin'}
다 아신다는 가정하에 handleChange 함수를 수정해보겠습니다.
event.target 의 name 과 value 를 둘 다 사용해야하기에 destructing 을 이용하여 간단하게 지정해주었습니다.
이제 세터함수 를 통해 state 값 을 변경해줘야합니다. 여기서 앞의 두 자바스크립트 지식을 써야 하는데요,
우리는 객체를 업데이트 시켜야 하기 때문에 spread operator 를 사용하여야 하며
사본을 만들고 난 후 그 사본의 상태를 세터함수 를 통해 변경하게 되는 것입니다.
const handleChange = (event) => {
const { name, value } = event.target;
setInputs({
...inputs,
[name]: value,
});
};inputs[name] = value 가 안되는 이유는 리액트의 불변성 때문인데요,
기존 상태를 이런 식으로 직접 수정하게 되면 값이 바뀌어도 리렌더링이 되지 않습니다.
또한 추후에 컴포넌트 성능 최적화를 위해서는 이 불변성을 지켜줘야 합니다.
이 부분에 대해 더 알고 싶으시다면 벨로퍼트님 블로그를 참고해주세요!
전체 코드 (클릭하면 볼 수 있음)
import React, { useState } from "react";
const WriteMessage = () => {
const [inputs, setInputs] = useState({
writer: "",
message: "",
});
// Destructing
const { writer, message } = inputs;
const handleChange = (event) => {
const { name, value } = event.target;
setInputs({
...inputs,
[name]: value,
});
};
const handleReset = () => {
setInputs({
writer: "",
message: "",
});
};
return (
<>
<input
type="text"
name="writer"
placeholder="What's your name ... 👩"
value={writer}
onChange={handleChange}
/>
<input
type="text"
name="message"
placeholder="Write Something ... 📝"
value={message}
onChange={handleChange}
/>
<button onClick={handleReset}>초기화</button>
<h1>작성자:{writer}</h1>
<h1>내용: {message}</h1>
</>
);
};
export default WriteMessage;순수 자바스크립트 를 사용 할 때 특정 DOM 을 선택해야 하는 상황에서는
getElementById, querySelector 와 같은 DOM Selector 함수 를 사용해서 DOM 을 선택합니다.
리액트 에서도 가끔씩 DOM 을 직접 선택해야 하는 상황이 있을 수 있습니다.
예를 들면
- 특정
input에 포커스 주기 - 스크롤 박스 조작하기
Canvas요소에 그림 그리기 등
이럴 때 리액트에서는 ref 라는 것을 사용합니다.
ref 를 사용하는 방법은 총 3가지 입니다.
원하는 방법을 골라서 사용하면 되지만 최근에는 함수형 컴포넌트와 함께 hooks 를 많이 쓰는 추세입니다.
- 함수형 컴포넌트:
useRef
import React, { useRef } from "react";
const RefExample = () => {
const input = useRef();
const handleFocus = () => {
input.current.focus();
};
return (
<div>
<input ref={input} />
<button onClick={handleFocus}>FOCUS!</button>
</div>
);
};
export default RefExample;-
클래스형 컴포넌트:
React.createRef,콜백함수React.createRef
import React, { Component } from "react"; class RefExample extends Component { input = React.createRef(); handleFocus = () => { this.input.current.focus(); }; render() { return ( <div> <input ref={this.input} /> <button onClick={this.handleFocus}>FOCUS!</button> </div> ); } } export default RefExample;
콜백함수
import React, { Component } from "react"; class RefExample extends Component { handleFocus = () => { this.input.focus(); }; render() { return ( <div> <input ref={(ref) => (this.input = ref)} /> <button onClick={this.handleFocus}>FOCUS!</button> </div> ); } } export default RefExample;
컴포넌트에도 ref 를 달 수가 있는데요, 이렇게 하면 컴포넌트 내부의 메소드 및 멤버 변수에도 접근할 수 있습니다.
우선 스크롤 박스를 만들어보겠습니다.
import React, { useRef } from "react";
const RefExample = () => {
const box = useRef();
const style = {
border: "1px solid black",
height: "300px",
width: "300px",
overflow: "auto",
position: "relative",
};
const innerStyle = {
width: "100%",
height: "650px",
background: "linear-gradient(white,black)",
};
return (
<div style={style} ref={box}>
<div style={innerStyle} />
</div>
);
};
export default RefExample;💢 설명 전 짚고 넘어가야할 것 💢
교재 예제는 모두 클래스형 컴포넌트에서 콜백함수로ref를 전달합니다.
이를 모두 함수형 컴포넌트로 바꾸는 과정에서useRef외에forwardRef,useImperativeHandle을 사용합니다.
Hook에 대한 자세한 내용은 8장에서 다루게 됩니다. 여기서는 쓰인Hook API만 간단하게 설명하겠습니다.
// 자식 컴포넌트
import React, { forwardRef, useImperativeHandle, useRef } from "react";
const RefExample = forwardRef((props, ref) => {
const box = useRef();
const style = {
border: "1px solid black",
height: "300px",
width: "300px",
overflow: "auto",
position: "relative",
};
const innerStyle = {
width: "100%",
height: "650px",
background: "linear-gradient(white,black)",
};
useImperativeHandle(ref, () => ({
scrollToBottom() {
const { scrollHeight, clientHeight } = box.current;
box.current.scrollTop = scrollHeight - clientHeight;
},
}));
return (
<div style={style} ref={box}>
<div style={innerStyle} />
</div>
);
});
export default RefExample;// 부모 컴포넌트
import { useRef } from "react";
import RefExample from "./components/RefExample";
function App() {
const scrollBox = useRef();
const handleDown = () => {
scrollBox.current.scrollToBottom();
};
return (
<>
<RefExample ref={scrollBox} />
<button onClick={handleDown}>맨 밑으로</button>
</>
);
}
export default App;하지만 이 코드는 가독성이 떨어진 뿐더러 굳이 자식 컴포넌트에 scrollToBottom 콜백함수를 넣을 필요는 없으므로 아래 첨부 된 코드가 좀 더 효율적이라고 할 수 있습니다.
(알려주신 예빈님 감사합니다!)
// 자식
import React, { forwardRef } from "react";
const RefExample = forwardRef((props, ref) => {
const style = {
border: "1px solid black",
height: "300px",
width: "300px",
overflow: "auto",
position: "relative",
};
const innerStyle = {
width: "100%",
height: "650px",
background: "linear-gradient(white,black)",
};
return (
<div style={style} ref={ref}>
<div style={innerStyle} />
</div>
);
});
export default RefExample;// 부모
import { useRef } from "react";
import RefExample from "./RefExample";
function App() {
const scrollBox = useRef();
const handleDown = () => {
scrollToBottom();
};
const scrollToBottom = () => {
const { scrollHeight, clientHeight } = scrollBox.current;
scrollBox.current.scrollTop = scrollHeight - clientHeight;
};
return (
<>
<RefExample ref={scrollBox} />
<button onClick={handleDown}>맨 밑으로</button>
</>
);
}
export default App;


