일반적인 HTML의 DOM 요소 네이밍은 id를 사용한다
<div id="my-element"></div>
특정 DOM 요소에 어떤 작업을 해야할 때 요소에 id를 달면 CSS에서 특정 id에 특정 스타일을 적용하거나 자바스크립트에서 해당 id를 가진 요소를 찾아 작업할 수 있다. 리액트에도 public/index.html 파일에 id가 root인 요소가 있다. 리액트에서는 ref 개념을 통해 프로젝트 내부에서 DOM에 이름을 달 수 있다.
ref를 사용하는 경우
ref는 DOM을 반드시 직접적으로 건드려야 할 때 사용한다. 리액트에서는 DOM에 접근하지 않아도 state를 통해 구현할 수 있기 때문이다.
입력값을 검증하는 컴포넌트를 예시로 만들어보자.
// src/ValidationSample.css
.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
// src/ValidationSample.tsx
import "./ValidationSample.css";
import { useState } from "react";
function ValidationSample() {
const [form, setForm] = useState({
password: "",
clicked: false,
validated: false,
});
const { password, clicked, validated } = form;
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const nextForm = {
...form,
[e.target.name]: e.target.value,
};
setForm(nextForm);
}
function handleButtonClick() {
const nextForm = {
...form,
clicked: true,
validated: password === "0000",
};
setForm(nextForm);
}
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") handleButtonClick();
};
return (
<div>
<input
type="password"
name="password"
value={password}
onChange={handleChange}
className={clicked ? (validated ? "success" : "failure") : ""}
onKeyPress={handleKeyPress}
/>
<button onClick={handleButtonClick}>검증하기</button>
</div>
);
}
export default ValidationSample;
위 코드는 입력칸에 '0000'이라는 사전 검증 값을 입력하면 초록색으로 입력칸이 변하고, 다른 값을 입력하면 빨간색으로 입력칸이 변하는 코드이다.
input에서 onChange 이벤트가 발생하면 handleChange를 호출해서 state의 password를 업데이트한다. button은 onClick 이벤트 혹은 엔터키를 눌렀을때의 handleKeyPress를 호출해 clicked 값을 참으로 변경한 뒤, validated 값을 검증 결과로 설정했다.
위의 코드는 state만으로 필요한 기능을 구현할 수 있었다. 하지만 state만으로는 해결할 수 없는 기능들도 있다.
- 특정 input에 포커스 주기
- 스크롤 박스 조작하기
- Canvas 요소에 그림 그리기 등
위와 같은 상황들은 DOM에 직접적으로 접근해야하는 상황들이다. 이럴 경우 ref를 사용하게 된다.
ref 사용
ref를 사용하는 방법은 두 가지가 있다.
- 콜백 함수를 통한 ref 설정
- createRef / useRef를 통한 ref 설정
먼저 콜백 함수를 통해서는 ref를 달고자하는 요소에 ref라는 콜백 함수를 props로 전달해주면 된다. 콜백 함수는 ref 값을 파라미터로 전달받으며, 함수 내부에서 컴포넌트의 멤버 변수로 ref를 설정해준다.
<input ref={(ref) => {this.input=ref}} />
이런식으로 설정하면 앞으로의 this.input은 input 요소의 DOM을 가리키게 된다.
createRef는 클래스형 컴포넌트에서 사용하고, useRef는 함수형 컴포넌트에서 사용하게 된다. 물론 함수형 컴포넌트에서도 createRef를 사용할 수는 있지만, createRef의 경우 리렌더링 될 때마다 ref값이 초기화 되는 상황이 발생할 수 있으므로 useRef 사용을 권장한다. 지금은 위 코드에서 함수형 컴포넌트를 만들었으므로, useRef 함수를 사용한다.
이 함수를 통해 입력칸에 값을 입력한 뒤, 검증하기 버튼을 누르거나 엔터 키를 누를 때, 다시 포커스가 입력칸으로 자동으로 넘어와 조금 더 사용자에게 편한 경험을 선사할 수 있다.
import "./ValidationSample.css";
import { useState, useRef } from "react";
import React from "react";
function ValidationSample() {
const [form, setForm] = useState({
password: "",
clicked: false,
validated: false,
});
const { password, clicked, validated } = form;
const input: React.RefObject<HTMLInputElement> =
useRef<HTMLInputElement>(null);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const nextForm = {
...form,
[e.target.name]: e.target.value,
};
setForm(nextForm);
}
function handleButtonClick() {
const nextForm = {
...form,
clicked: true,
validated: password === "0000",
};
setForm(nextForm);
if (input.current !== null) input.current.focus();
}
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") handleButtonClick();
};
return (
<div>
{input !== null ? (
<div>
<input
ref={(ref) => input}
type="password"
name="password"
value={password}
onChange={handleChange}
className={clicked ? (validated ? "success" : "failure") : ""}
onKeyPress={handleKeyPress}
/>
<button onClick={handleButtonClick}>검증하기</button>
</div>
) : null}
</div>
);
}
export default ValidationSample;
코드에 useRef 사용한 부분을 추가했다. 이 코드로 다시 렌더링해보면, 원하는 기능이 추가되었음을 알 수 있다.
useRef는 리액트 훅 중 하나로, 함수형 컴포넌트에서 DOM 요소에 접근하기 위해 사용된다. useRef로 생성된 객체는 컴포넌트가 렌더링 될 때마다 새로 생성되지 않고, 이전 값을 유지한다. 따라서 해당 DOM 요소를 효율적으로 참조할 수 있다.
import { useRef } from "react";
function MyComponent() {
const buttonRef = useRef<HTMLButtonElement>(null);
function handleClick() {
if (buttonRef.current) {
buttonRef.current.textContent = "Clicked!";
}
}
return (
<div>
<button ref={buttonRef} onClick={handleClick}>
Click me
</button>
</div>
);
}
function App() {
return (
<div>
<MyComponent />
</div>
);
}
export default App;
위 코드는 React에서 useRef Hook을 사용하는 방법을 보여주는 예제이다.
useRef Hook은 React에서 참조를 유지하기 위해 사용되며, 일반적으로 DOM 요소에 대한 참조를 저장하는 데 사용된다.
이 예제에서는 MyComponent 함수 컴포넌트를 정의하고, 그 안에서 buttonRef라는 useRef Hook을 사용한다. 이 useRef Hook은 초기 값으로 null을 가지며, button 요소의 참조를 저장한다.
MyComponent 컴포넌트에서는 handleClick이라는 함수가 정의되고, 이 함수는 buttonRef의 current 속성을 사용하여 버튼 요소를 참조하고, 버튼 요소의 textContent를 "Clicked!"로 변경된다.
컴포넌트 ref
리액트에서는 컴포넌트에도 ref를 달 수 있다. 컴포넌트에 ref 다는 방법은 DOM에 ref 다는것과 동일하다. 컴포넌트를 통해 스크롤바를 구현해보자.
// src/ScrollBox.tsx
import React, { useRef } from "react";
const ScrollBox = () => {
const boxRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
if (!boxRef.current) return;
const { scrollHeight, clientHeight } = boxRef.current;
boxRef.current.scrollTop = scrollHeight - clientHeight;
};
const scrollToTop = () => {
if (!boxRef.current) return;
boxRef.current.scrollTop = 0;
};
const style: React.CSSProperties = {
border: "1px solid black",
height: "300px",
width: "300px",
overflow: "auto",
position: "relative",
};
const innerStyle: React.CSSProperties = {
width: "100%",
height: "650px",
background: "linear-gradient(white, black)",
};
return (
<div style={style} ref={boxRef}>
<button onClick={scrollToBottom}>맨 밑으로</button>
<div style={innerStyle} />
<button onClick={scrollToTop}>맨 위로</button>
</div>
);
};
export default ScrollBox;
// src/App.tsx
import React from "react";
import ScrollBox from "./ScrollBox";
const App = () => {
return (
<div>
<ScrollBox />
</div>
);
};
export default App;
ScrollBox 컴포넌트는 div요소를 렌더링 하는데, style 속성을 적용해서 스크롤 바가 포함된 박스를 만들어준다. 이 박스는 ref를 사용해서 boxRef라는 useRef Hook으로 연결된다.
박스의 맨 위와 맨 아래쪽에는 맨 밑으로 바로 향하는 버튼과 맨 위로 바로 향하는 버튼이 scrollToBottom, scrollToTop으로 구현되어있다. 또, innerStyle 속성에서 linear-gradient를 통해 박스 내부의 그라데이션 효과를 표현했다.
ref를 통해 다른 컴포넌트에서 전달받은 메서드를 실행하는 방식은 리액트의 사상에서는 어긋난 방법일 수 있다. 따라서 ref를 사용하지 않고도 구현이 가능한지 반드시 고려해보는것을 권장한다.
'WEB > React' 카테고리의 다른 글
리액트 라이프사이클 이해하기 [react/life cycle/typescript] (0) | 2023.03.13 |
---|---|
리액트 컴포넌트 반복 사용하기 [react/component/typescript] (0) | 2023.03.12 |
리액트 이벤트 시스템 이해하기 [react/event/typescript] (0) | 2023.03.11 |
리액트 컴포넌트 이해하기 [react/component/typescript] (0) | 2023.03.11 |
JSX 이해하기 (0) | 2023.03.11 |