이벤트란 웹 페이지에서 일어나는 사용자의 행위로 버튼 클릭, 페이지 스크롤, 새로고침 등의 행위에 해당합니다.
ex. 리액트 사용하지 않고 HTML과 자바스크립트만으로 이벤트를 핸들링
<script>
function handleOnClick() {
alert("button clicked!");
}
</script>
<button onclick="handleOnClick()">
Click Me!
</button>
ex. Body 컴포넌트에 버튼 하나 만들고 버튼을 클릭하는 이벤트가 발생하면 실행되는 이벤트 핸들러를 만듭니다.
function Body() {
function handleOnClick() {
alert("버튼을 클릭하셨군요!");
} // 함수 선언
return (
<div className="body">
<button onClick={handleOnClick}>클릭하세요</button>
</div>
);// 버튼 클릭시 클릭 이벤트 처리하는 이벤트 핸들러 함수 handleOnClick 호출
}
export default Body;
리액트에서 이벤트 발생하면 이벤트 핸들러에게 이벤트 객체를 매개변수로 전달합니다.
이벤트 객체는 이벤트가 어떤 요소에서 어떻게 발생했는지에 관한 정보가 상세히 담겨 있습니다.
ex. Body 컴포넌트에 2개의 버튼을 만들고, 이벤트가 발생하면 클릭한 버튼의 이름을 콘솔에 출력하도록 수정합니다.
function Body() {
function handleOnClick(e) {
console.log(e.target.name);
} // 이벤트 객체(e)를 매개변수로 저장
return (
<div className="body">
<button name="A버튼" onClick={handleOnClick}>
A 버튼
</button>
<button name="B버튼" onClick={handleOnClick}>
B 버튼
</button>
</div>
);
}
export default Body;
+ 이벤트 객체에는 이벤트 처리하는데 필요한 많은 정보를 담고 있습니다.
정확히 어떤 값들이 저장되어있는지 알아보기 위해 Body 컴포넌트에서 작성한 함수 handleOnClick을 수정합니다.
function Body() {
function handleOnClick(e) {
console.log(e); // 이벤트 객체 e를 콘솔에 출력
console.log(e.target.name);
}
return (
<div className="body">
<button name="A버튼" onClick={handleOnClick}>
A 버튼
</button>
<button name="B버튼" onClick={handleOnClick}>
B 버튼
</button>
</div>
);
}
export default Body;
→ 콘솔에 출력된 이벤트 객체를 보면 많은 프로퍼티가 저장되어 있음을 알 수 있습니다. 실무에선 1~2개의 값만 활용하므로 모두 상세히 알 필요는 없습니다.
지금부터 사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 리액트 컴포넌트를 만들 것인데 이를 위해선 리액트의 핵심 기능 중 하나인 State를 알아야 합니다.
State는 상태라는 뜻으로 리액트 컴포넌트의 State와 전구의 상태는 매우 유사합니다.
useState로 State 생성
// useState의 문법
const [light, setLight] = useState('off');
State 변수 set 함수 생성자(초깃값)
ex. State 변수 count를 생성해 Body 컴포넌트에서 숫자를 카운트할 수 있도록 Body,js를 수정합니다.
import { useState } from "react";
// react에서 useState 불러와야 사용 가능
function Body() {
const [count, setCount] = useState(0); // State의 초깃값으로 0 전달
return (
<div>
<h2>{count}</h2>
</div>
);
}
export default Body;
set 함수로 State 값 변경
ex. 버튼 클릭할 때마다 State( count ) 값을 1씩 늘려나가도록 Body 컴포넌트를 수정합니다.
import { useState } from "react";
function Body() {
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
}; // 함수 onincrease는 setCount 호출해 count+1 값 전달 => State 값 1증가
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
</div>
);
}
export default Body;
+ 컴포넌트가 페이지에 렌더링하는 값은 컴포넌트 함수의 반환값입니다. 따라서 컴포넌트를 다시 렌더링한다는 것은 컴포넌트 함수를 다시 호출한다는 의미와 같습니다.
ex. Body를 호출할 때마다 콘솔에 문자열 Update!를 출력하도록 Body 컴포넌트를 수정해 컴포넌트를 다시 호출한다는 의미를 확인합니다.
import { useState } from "react";
function Body() {
console.log("Update!"); // Body 호출할 때마다 콘솔에 Update! 출력
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
</div>
);
}
export default Body;
+ HTML 입력 폼 만드는 태그
- Input → 다양한 형식의 정보 입력 가능한 태그
- Select → 여러 옵션에서 하나를 선택하도록 드롭다운 목록 보여주늩 태그
- Textarea → 여러 줄의 텍스트를 입력할 수 있는 태그
⇒ 리액트 State 이용시 다양한 입력 폼에서 제공되는 사용자 정보를 효과적으로 처리할 수 있습니다.
+ <input> 태그 → type 속성에 따라 다양한 입력 폼 생성이 가능합니다.
<input> 태그로 텍스트 입력하기
ex. <input> 태그로 텍스트 입력 폼 생성해 입력할 때마다 콘솔에 출력하도록 이벤트 핸들러 구현합니다.
import { useState } from "react";
function Body() {
const handleOnChange = (e) => {
console.log(e.target.value);
// 이벤트 객체를 매개변수로 저장해 폼에 입력한 값을 콘솔에 출력
};
return (
<div>
<input onChange={handleOnChange} />
</div>
);// onChange 이벤트 - 사용자가 입력 폼에서 텍스트 입력하면 바로 발생
}
export default Body;
지금 코드는 사용자가 입력한 텍스트가 State에 저장되어있지 않습니다. 따라서 버튼을 클릭했을 때 사용자가 입력한 텍스트를 콘솔에 출력하는 등의 동작을 수행하게 하려면 돔 API를 이용하는 등 번거로운 작업이 별도로 요구됩니다.
ex. State 하나 만들어 사용자가 폼에서 입력할 때마다 텍스트를 State 값으로 저장하도록 Body 컴포넌트를 수정합니다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
// State 변수 text 생성 -> 빈 문자열을 초기값으로 지정
const handleOnChange = (e) => {
setText(e.target.value);
// 텍스트 변경할 때마다 set함수 호출해 text값을 현재 입력한 텍스르로 변경
};
return (
// input의 value 속성에 변수 text 설정 & {text}로 text 값을 페이지에 렌더링
<div>
<input value={text} onChange={handleOnChange} />
<div>{text}</div>
</div>
);
}
export default Body;
<input> 태그로 날짜 입력하기
type 속성을 date로 설정하면 날짜 형식의 데이터 입력이 가능합니다.
ex. State 이용해 날짜 형식의 데이터를 입력 정보로 받아오도록 Body 컴포넌트를 수정합니다.
import { useState } from "react";
function Body() {
const [date, setDate] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setDate(e.target.value);
};
return (
<div>
<input type="date" value={date} onChange={handleOnChange} />
</div>
);
}
export default Body;
<select> 태그로 드롭다운 상자로 여러 옵션 중 하나 선택하기
<select> 태그는 <option>과 함께 사용하며 드롭다운 메뉴로 여러 목록을 나열해 보여 주는 입력 폼이 만들어집니다.
ex. 드롭다운 입력 폼에서 입력한 값을 State로 어떻게 처리하는지 알아보기 위해 Body 컴포넌트를 수정하겠습니다.
import { useState } from "react";
function Body() {
const [option, setOption] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setOption(e.target.value);
};
return (
<div>
<select value={option} onChange={handleOnChange}>
<option key={"1번"}>1번</option>
<option key={"2번"}>2번</option>
<option key={"3번"}>3번</option>
</select>
</div>
);
}
export default Body;
<textarea> 태그로 글상자로 여러 줄의 텍스트 입력하기
<textarea> 태그는 사용자가 여러 줄의 텍스트를 입력할 때 사용하는 폼을 만듭니다.
ex. 글 상자에 입력한 내용을 State로 어떻게 처리하는지 알아보기 위해 Body 컴포넌트를 수정하겠습니다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값 : ", e.target.value);
setText(e.target.value);
};
return (
<div>
<textarea value={text} onChange={handleOnChange} />
</div>
);
}
export default Body;
여러 개의 사용자 입력 관리하기
ex. 4개의 State 변수와 이벤트 핸들러를 생성해 이름, 성별, 출생 연도, 자기소개 등을 한 번에 입력할 수 있도록 Body 컴포넌트 수정합니다.
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
const [gender, setGender] = useState("");
const [birth, setBirth] = useState("");
const [bio, setBio] = useState("");
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeGender = (e) => {
setGender(e.target.value);
};
const onChangeBirth = (e) => {
setBirth(e.target.value);
};
const onChangeBio = (e) => {
setBio(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} placeholder="이름" />
</div>
<div>
<select value={gender} onChange={onChangeGender}>
<option key={""}></option>
<option key={"남성"}>남성</option>
<option key={"여성"}>여성</option>
</select>
</div>
<div>
<input type="date" value={birth} onChange={onChangeBirth} />
</div>
<div>
<textarea value={bio} onChange={onChangeBio} />
</div>
</div>
);
/*
input 태그 입력 폼에서 이름 받고 State(name)으로 관리
input 태그 드롭다운 폼에서 성별 받고 State(gender)로 관리
date인 input 태그 입력 폼에서 생년월일 받고 State(birth)로 관리
textarea 태그의 글상자에서 자기소개 내용 받고 State(bio)로 관리
*/
}
export default Body;
ex. 객체 자료형을 이용해 여러 입력 정보를 하나의 State에서 관리할 수 있도록 Body 컴포넌트를 수정하겠습니다.
import { useState } from "react";
function Body() {
const [state, setState] = useState({
name: "",
gender: "",
birth: "",
bio: "",
}); // 객체 state에 name, gender, birth, bio 프로퍼티 있고 초기값은 공백 문자열
const handleOnChange = (e) => {
console.log("현재 수정 대상:", e.target.name);
console.log("수정값:", e.target.value);
setState({
...state,
[e.target.name]: e.target.value,
});
};
return (
<div>
<div>
<input
name="name"
value={state.name}
onChange={handleOnChange}
placeholder="이름"
/>
</div>
<div>
<select name="gender" value={state.gender} onChange={handleOnChange}>
<option key={""}></option>
<option key={"남성"}>남성</option>
<option key={"여성"}>여성</option>
</select>
</div>
<div>
<input
name="birth"
type="date"
value={state.birth}
onChange={handleOnChange}
/>
</div>
<div>
<textarea name="bio" value={state.bio} onChange={handleOnChange} />
</div>
</div>
);
}
export default Body;
+ 사용자의 입력 처리할 이벤트 핸들러 handleOnChange
- setState에서 새로운 객체 생성해 전달합니다.
→ 스프레드 연산자 이용해 기존 객체 state의 값 나열합니다.
→ 객체의 괄호 표기법 사용해 name 속성을 key로, 입력한 값을 value로 저장합니다.
// 이벤트 핸들러는 이벤트 객체 e를 매개변수로 저장 후 setState 호출
setState({
...state,
[e.target.name]: e.target.value,
});
동적으로 변하는 값인 리액트의 State 역시 일종의 값이므로 Props로 전달할 수 있습니다.
ex. Body에 자식 컴포넌트를 만들고, Body의 State를 Props로 전달하도록 Body.js를 수정합니다.
import "./Body.css";
import { useState } from "react";
// Props로 Body 컴포넌트의 State 변수 number가 전달됨
function Viewer({ number }) {
return <div>{number % 2 === 0 ? <h3>짝수</h3> : <h3>홀수</h3>}</div>;
}
// Viewer를 자식 컴포넌트로 사용 -> Props로 변수 number 전달
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number - 1);
};
return (
<div>
<h2>{number}</h2>
<Viewer number={number} />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
리액트에서는 부모가 자식에게 State를 Props로 전달했는지와 상관없이 부모 컴포넌트가 리렌더하면 자식도 함께 리렌더됩니다.
ex. 부모 컴포넌트가 자식에게 State를 Props로 전달하지 않는 경우에도 함께 리렌더되는지 확인해봅시다.
import { useState } from "react";
function Viewer() { // Props를 받지 않음
console.log("viewer component update!");
return <div>Viewer</div>;
}
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number - 1);
};
return (
<div>
<h2>{number}</h2>
// Body도 Viewer 컴포넌트에 Props로 State 전달x
<Viewer />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
첫 번째 출력은 처음 렌더링할 때 출력된 것이고 나머지 5번은 부모인 Body 컴포넌트의 State가 변할 때마다 출력되었음을 확인할 수 있습니다.
⇒ 부모 컴포넌트가 리렌더하면 자식도 함께 리렌더됩니다.
+ 부모와 자식은 함께 리렌더되기 때문에 의미 없는 리렌더가 발생할 확률이 높고 의미 없는 리렌더가 자주 발생하면 웹 브라우저의 성능은 떨어집니다. 따라 컴포넌트의 부모-자식 관계에서 State를 사용할 때는 늘 주의가 필요합니다.
Ref는 Reference의 줄임말로 참조를 뜻하며 리액트의 Ref를 이용하면 돔 요소들을 직접 조작할 수 있습니다.
Ref 객체는 useRef라는 리액트 함수를 이용해 생성 가능합니다.
ex. Body 컴포넌트를 수정한 후 돔 요소의 하나인 <input> 태그의 입력 폼에 접근하는 Ref를 만들도록 Body.js를 수정합니다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
// 인수로 전달한 값을 초깃값으로 하는 Ref 객체 생성해 textRef에 저장
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text); // 작성한 텍스트를 메시지 대화상자에 표시함
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
ex. useRef를 이용해 텍스트 입력 폼을 초기화하도록 Body 컴포넌트를 수정합니다.
import { useRef, useState } from "react";
function Body() {
(...)
const handleOnClick = () => {
alert(text);
textRef.current.value = "";
// textRef.current -> textRef가 현재 참조하고 있는 돔 요소
};
(...)
}
export default Body;
→ 버튼 클릭해 핸들러 실행 후 대화상자에서 확인 버튼 클릭하면 textRef.current.value의 value 값이 공백 문자열로 초기화됩니다.
웹 서비스에서는 사용자가 특정 폼에 내용을 입력하지 않거나 내용이 정해진 길이보다 짧은 경우 해당 폼에 포커스하여 사용자의 추가 입력을 유도합니다. → 리액트의 Ref 기능 이용하면 특정 요소에 포커스 기능을 지정할 수 있습니다.
ex. 텍스트 입력 폼에서 사용자가 문자를 다섯 글자 미만으로 입력하면 해당 요소에 포커스한 상태로 추가 입력을 기다리도록 Body 컴포넌트를 수정합니다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
if (text.length < 5) {
textRef.current.focus(); // 다섯 글자보다 적으면 포커스 실행 & 초기화x
} else {
alert(text);
setText("");
// 입력 값 초기화 위해 set 함수인 setText 호출 후 인수로 빈 문자열 전달
}
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
+ 리액트 훅( React Hook )
함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용하도록 도와주는 함수들입니다.
1. 좋아요 버튼을 만들어 버튼 클릭시 좋아요가 올라가게 만들기 - LikeButton 컴포넌트 생성 후 State 활용해 작성
import React, { useState } from "react";
function LikeButton() {
const [likes, setLikes] = useState(0);
const handleLikeClick = () => {
setLikes(likes + 1);
};
return (
<div>
<p>현재 좋아요 개수: {likes}</p>
<button onClick={handleLikeClick}>❤️</button>
</div>
);
}
export default LikeButton;
2. 간단한 소개하는 텍스트 폼 생성하기 - 8글자 미만으로 작성시 포커스, 8글자 이상 입력되고 버튼 클릭시 작성한 내용과 그 내용 한 줄 밑에 "간단한 소개 설정 성공!" 이라고 메시지 창에 출력 후 입력 폼 초기화
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
if (text.length < 8) {
textRef.current.focus();
} else {
alert(text + "\n간단한 소개 설정 성공!");
setText("");
}
};
return (
<div>
<span>간단한 소개 </span>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)
Corner React.js 1
Editor: ssxbin
[React.js 1] 8장. Hooks (0) | 2023.11.24 |
---|---|
[React.js 1] p1. 카운터 앱 만들기, 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2023.11.17 |
[React.js 1] 5장. 리액트의 기본 기능 다루기(1) (0) | 2023.11.03 |
[React.js 1]3장, 4장 Node.js, 리액트 시작하기 (0) | 2023.10.13 |
[React.js 1] 2장 자바스크립트 실전 (0) | 2023.10.06 |