리액트에서 말하는 이벤트는 웹페이지에서 일어나는 사용자의 행위를 뜻한다. 페이지를 스크롤하는 기능, 버튼을 클릭하는 기능 등이 이벤트이며, 이번 파트에서는 리액트에서 어떻게 이벤트를 처리하는지 알아보겠다.
1-(1) 이벤트 핸들링과 이벤트 핸들러
이벤트 핸들링은 이벤트가 발생하면 특정 코드가 동작하도록 만드는 작업이다. 페이지의 버튼을 클릭하는 이벤트가 발생하면 "버튼을 클릭했다"라는 메시지 대화상자를 띄우는 코드는 다음과 같다.
function handleOnClick(){
alert("버튼을 클릭했다")
}
return (
<div className="body">
<button onClick={handleOnClick}>클릭하세요</button>
</div>
)
이벤트 핸들러 함수 handleOnClick을 선언하면, 생성된 버튼을 클릭 시 핸들러 함수 handleOnClick이 호출된다. 결과는 다음과 같다.
클릭하세요 버튼을 클릭하면 '버튼을 클릭했다'는 메시지 대화상자가 뜬다.
1-(2) 이벤트 객체 사용하기
1-(1)처럼 이벤트가 발생하면 이벤트 핸들러에게 이벤트 객체를 매개변수로 전달하게 된다. 이벤트 객체에는 이벤트가 어떤 요소에서 어떻게 발생했는지에 관한 정보가 담겨 있다. 2개의 버튼을 만들고 이벤트가 발생할 시 클릭한 버튼의 이름을 콘솔에 출력하는 코드를 작성해보자. 이때 console.log(e.target.name); 코드를 활용하면 현재 이벤트가 발생한 요소의 name 속성값을 콘솔창에 출력할 수 있다.
function Body() {
function handleOnClick(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>
);
}
다음과 같이 A 버튼을 클릭하면 e.target에 A버튼이 저장되고, B버튼을 클릭하면 e.target에 B버튼이 저장됨을 콘솔창에서 확인할 수 있다. 만일 버튼을 2번 이상 연속으로 클릭하게 되면 그림과 같이 콘솔창에 연속 클릭 횟수가 보이게 된다.
console.log(e); 코드를 추가하게 되면, 이벤트 객체에 어떤 값들이 저장되어 있는지 정확하게 콘솔창에 뜨게 된다. 이때 console.log(e);는 핸들러 함수 handleOnClick을 실행했을 때 매개변수에 저장되는 이벤트 객체 e를 콘솔에 출력하는 코드이다.
이번 파트에서는 사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 리액트 컴포넌트를 만드는 법에 대해서 알아보겠다. 여기서 반드시 알아야 하는 개념이 있는데, 바로 State이다. state는 상태라는 뜻의 영어 단어로, 리액트에서 사용하는 state는 전구로 비유할 수 있다. 전구 state는 소등과 점등 중 하나의 값을 가지는데, 값이 소등일 때 스위치를 켜면 값이 점등으로 바뀐다. 이렇게 전구의 상태가 상태 변화에 따라 값이 변하는 것처럼 리액트 컴포넌트 또한 state 값에 따라 다른 결과를 보이게 된다.
2-(1) state 사용법
리액트에서는 함수 useState로 state를 생성할 수 있다. useState를 호출하면 2개의 요소가 담긴 배열을 반환하게 된다. 앞서 설명한 전구 state 비유를 가져와 작성한 state의 기본 문법은 다음과 같다.
const [light, setLight] = useState('off');
State 변수 set 함수 생성자(초깃값)
첫 번째 요소인 light는 현재 상태의 값을 저장하고 있는 변수로 state 변수라 불린다. 두 번째 요소 setLight는 state 변수의 값을 변경하는 함수로, set함수라 불린다. 위에 작성한 코드처럼 off를 인수로 전달하면, state 변수 light의 초깃값은 off가 된다.
function Body() {
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
</div>
);
}
다음처럼 인수 0을 전달하는 State 변수 count를 생성하였다. + 버튼을 클릭할 때마다 state(count) 값을 1씩 늘어가는 걸 볼 수 있다. 이렇게 반응하는 이유는, 함수 onIncrease가 setCount를 호출하고, 인수로 현재의 count 값에 1 더한 값을 전달하기 때문에 state(count)값이 1 증가하게 되는 것이다. 이처럼 set함수를 호출해 state 값을 변경하면, 변경값을 페이지에 반영하기 위해 리액트는 컴포넌트를 다시 렌더링하게 된다. 이것을 '컴포넌트의 업데이트'라고 표현한다.
2-(2) state로 사용자 입력 관리하기
웹사이트에서는 다양한 입력 폼이 존재하는데, 이번에는 input 태그로 텍스트를 입력하고 입력한 텍스트를 그대로 렌더링하는 코드를 만들어 보겠다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
setText(e.target.value);
};
return (
<div>
<input value={text} onChange={handleOnChange} />
<div>{text}</div>
</div>
);
}
export default Body;
State를 하나 만든 뒤, 사용자가 폼에서 입력할 때마다 텍스트를 state 값으로 저장하는 코드이다. const [text, setText] = useState("");로 빈 문자열을 초깃값으로 하는 state 변수 text를 생성한 뒤, 폼에 입력한 텍스트가 변경될 때마다 set 함수를 호출해 text 값을 현재 입력한 텍스트로 변경한다. 이후 input 태그의 value 속성에 변수 text를 설정하여 값을 페이지에 렌더링하게 만든다.
코드가 잘 작동되었다면 위와 같은 결과를 볼 수 있다. 이번에는 각종 태그들을 활용하여 이름, 성별, 출생 연도, 자기소개를 한번에 입력받는 코드를 만들어보겠다.
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>
);
}
export default Body;
input 태그의 입력 폼에서 이름을 받고 state name으로 관리한다. 그 다음 select 태그의 드롭다운 폼에서 여성 또는 남성 성별을 받아서 state(gender)로 관리한다. date type인 input 태그의 입력 폼에서 날짜를 받고 state(birth)로 관리한다. 마지막으로 textarea 태그의 글상자에서 자기 소개 내용을 입력 받은 후 state(bio)로 관리하는 코드이다. 잘 적용이 되었다면 아래와 같은 모습을 확인할 수 있다.
물론 const[name, setName]처럼 하나하나 입력을 받고 관리할 필요는 없다. 아예 하나의 const[state, setState]=useState를 활용하여 한꺼번에 관리가 가능하다. 왼쪽이 수정 전, 오른쪽이 수정 후 코드이다.
2-(3) Props와 State
이렇게 동적으로 변하는 state 역시 일종의 값이므로 props로 전달할 수 있다. + 버튼을 누르면 숫자가 1 올라가고 - 버튼을 누르면 숫자 1 줄어드는데, 숫자가 홀수일 경우 홀수가, 짝수일 경우 짝수 글자가 뜨는 코드를 다음과 같이 작성하였다.
import { useState } from "react";
function Viewer({ number }) {
return <div>{number % 2 === 0 ? <h3>짝수</h3> : <h3>홀수</h3>}</div>;
}
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;
우선, viewer 컴포넌트를 선언한다. 이 컴포넌트에는 props로 body 컴포넌트에 있는 state 변수 number가 전달된다. viewer 컴포넌트는 조건부 렌더링을 이용해 변수 number의 값을 확인한다. 만일 값이 홀수이면 홀수를, 짝수이면 짝수라는 글을 페이지에 렌더링하게 된다. 이때 body에서 viewer를 자식 컴포넌트로 사용하며 props로 변수 number을 전달하게 된다.
이처럼 자식 컴포넌트는 props로 전달된 state 값이 변하면 자신도 리렌더된다. 부모에 속해 있는 state(number) 값이 변하면 viewer 컴포넌트에서 구현한 '짝수', '홀수' 값도 따라서 변하게 된다. 여기서 잘 알아두어야 하는 점이 있다. 부모 컴포넌트가 자식에게 state를 props로 전달하지 않는 경우에도, 부모 컴포넌트의 state가 변하면 자식 컴포넌트로 리렌더가 된다. 의미 없는 리렌더가 자주 발생할수록 웹 브라우저의 성능은 떨어지게 된다. 그러므로 웹 브라우저의 성능을 효율적으로 유지하고 싶다면 부모-자식 관계에서 state를 사용할 때 주의를 기울여야 한다.
Ref는 reference의 줄임말로 참조라는 뜻을 가진 영어 단어이다. 이 Ref를 이용하며 돔 요소들을 직접 조작할 수 있다. 이번 파트에서는 ref 기능을 이용해 돔 요소를 제어하는 법을 알아보겠다. 여기서 복습하자면, 돔(DOM)이란 사용자 인터페이스를 구성하는 기본 단위로, UI를 효과적으로 구축하고 관리할 수 있게 도와주는 요소이다.
3-(1) useRef 사용하기
Ref를 사용하려면 useRef라는 리액트 함수를 이용해 Ref 객체를 생성해야 한다. 1-(1) 이벤트 핸들링과 이벤트 핸들러에서 버튼을 눌렀을 때 메시지 대화상자를 띄우는 코드를 기억하는가? 이를 활용하여 글을 쓰고 버튼을 클릭했을 때 썼던 글을 메시지 대화상자에 띄우는 코드를 작성해보겠다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text);
};
return (
<div>
<input value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
텍스트 박스에 글을 쓰고 '작성 완료' 버튼을 클릭하면 메시지 대화상자에 썼던 글이 그대로 뜨게 된다. 이 상태에서 input 태그의 입력 폼에 접근하는 Ref를 만들어보자.
useRef를 임포트하고,
인수로 전달한 값을 초깃값으로 하는 Ref 객체를 생성하고 이 생성한 Ref를 상수 textRef에 저장하는 코드를 입력한다. 이후 input 태그에서 ref={textRef}를 추가하여 textRef가 입력 폼에 접근하도록 설정한다. 이제 이 textRef를 이용해서 입력 폼을 직접 조작할 수 있다.
이렇게 만든 textRef를 이용해서 텍스트 입력폼을 초기화하는 코드를 작성하였다.
글을 작성하고 작성 완료 버튼을 누른 뒤 메시지 대화상자에 썼던 글이 뜨는 과정까지는 동일하다. 그러나 메시지 대화상자의 '확인' 버튼을 클릭하게 되면 textRef.current.value="";에 작성했던 것처럼 텍스트 박스에 썼던 글이 초기화가 된다.
3-(2) useRef로 포커스하기
웹 서비스에서는 사용자가 특정 폼에 내용을 입력하지 않거나 길이를 너무 짧게 작성하면 해당 폼을 포커스하여 사용자의 추가 입력을 유도하게 된다. 리액트의 Ref 기능을 이용해서 방금 설명한 동작을 수행해보겠다.
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 < 7) {
textRef.current.focus();
} else {
alert(text);
setText("");
}
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
위 코드는 텍스트 박스에 글을 작성하고 작성완료를 누르면 작성했던 글이 그대로 메시지 대화상자에 뜨는, 아까와 동일한 동작을 수행하는 코드이다. 하지만 텍스트 길이가 7보다 작으면
이 코드를 통해 alert(text)로 넘어가지 않고 텍스트 박스를 포커스한 상태로 입력 글이 7자 이상이 될때까지 대기하게 된다.
3-(3) 리액트 훅
앞서 설명한 state를 만드는 함수인 useState와 참조 객체를 만드는 함수인 useRef 모두 리액트 훅에 해당한다. 이 리액트 훅은 위에 작성한 동작들을 보면 알겠지만, 함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용하도록 도와준다. 일반 함수와 리액트 훅을 구별하려면 이름 앞에 use가 들어갔는지 확인해보면 된다. 이 리액트 훅은 2018년도에 처음 발표된 비교적 최신 기능이며 useState, useRef 말고도 다양한 훅이 존재한다.
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).
Corner React.js1
Editor: Ijin
[React.js 1팀] 8장. Hooks (1) | 2024.11.29 |
---|---|
[React.js 1팀] project 1 [카운터] 앱 만들기 ~ 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2024.11.22 |
[React.js 1팀] 5장 리액트의 기본 기능 다루기 (1) (1) | 2024.11.08 |
[React.js 1팀] 03장. Node.js (0) | 2024.10.11 |
[React.js 1팀] 4장. 리액트 시작하기 (0) | 2024.10.11 |