사진으로는 잘 안보이지만, 아이콘에 커서를 갖다대면 '예약 확인 중 입니다' 라는 문구가 나온다. 생각해보니 이 기능이 앞으로 여러군데 쓰일 것 같아서 이참에 hook 으로 만들어야겠다는 생각이 들었다.


내가 직접 고민해서 hook 을 만들어 보는게 제일 좋지만, 어떻게 만들어야 할지 감이 전혀 없어서 구글에 useHover 를 검색해봤다. 그러자 아래 코드를 많이 사용하는 듯 보였다. 근데 봐도봐도 이게 어떻게 동작하는거지?? 라는 생각이 들어 하나씩 뜯어보며 공부해보기로 했다.
<useHook 컴포넌트>
import { useState, useRef, useEffect } from "react";
export const useHover = () => {
const [isHover, setIsHover] = useState(false);
const ref = useRef(null);
const handleMouseOver = () => setIsHover(true);
const handleMouseOut = () => setIsHover(false);
useEffect(() => {
const element = ref.current;
if (element) {
element.addEventListener("mouseover", handleMouseOver);
element.addEventListener("mouseout", handleMouseOut);
}
return () => {
if (element) {
element.removeEventListener("mouseover", handleMouseOver);
element.removeEventListener("mouseout", handleMouseOut);
}
};
}, []);
return { ref, isHover };
};
사용할때는 아래와 같이 가져다 쓰면 된다.
const Box = styled.div`
width: 100px;
height: 100px;
background-color: red;
`;
export const Default = () => {
const {ref, isHover} = useHover();
return (
<>
<Box ref={ref} />
{isHover ? <div>ToolTip!</div> : null}
</>
);
};
출처: https://ghost4551.tistory.com/178 [프론트엔드 개발자의 기록 공간:티스토리]
훅에서 반환하는 ref 를 받아와서
DOM 요소 (여기서는 <Box> styled-component) 에 연결시켜주면,
해당 DOM 의 mouseover 하는 경우, DOM 요소에 진입했다고 판단하여 isHover 를 treu 로 DOM 요소를 벗어나면 false 를 준다.
그럼 훅에서 반환하는 isHover 를 받아와 true 일때의 원하는 텍스트를 보여준다.
useHover 의 핵심 동작은 저러한데, 위 코드에서 이해가 안갔던 부분이 바로 useRef 의 역할이다. 굉장히 단순한 코드인데 어떻게 동작하는건지 이해가 가지 않았다.
여기서 내가 useRef 에 대해 잘 모르고 있다는 생각이 들어 이번 기회에 useRef 에 대해 자세히 찾아보게 되었다.
useRef 는 매번 사용할때마다 폭풍 검색을 하게 만드는 녀석이다😂 잘쓰면 매우 유용한 react hook 같은데, useState 보다 사용하기가 다소 까다롭다고 느껴져 사실 기피했던 경향이 있었다. 언젠가는 한번 공부를 해봐야 겠다는 생각이 있었는데 이번 기회에 useRef 를 공부해봤다.
useRef 의 대표적인 활용 방법은 아래와 같다.
useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook입니다.
React 는 자주 바뀌는 값을 state 로 정의하고 state 가 변할때마다 알아서 렌더링을 하여 항상 우리는 새로운 state 를 볼 수 있는 것이 기본적인 리액트의 흐름이다.
그러나 useRef는 초깃값으로 설정된 값을 useRef 객체의 .current 프로퍼티에 저장하고, 그 값은 변경해도 재렌더링이 발생하지 않아 항상 같은 값을 유지할 수 있게 해주는 hook 이다.
따라서 이러한 useRef 의 기능을 이용해 로컬 변수 용도로 사용할 수 있다.
간단한 예시를 살펴보자.
function App() {
const [state, setState] = useState(0);
const obj = { num: 0 };
let num = 0;
const addNumHandler = () => {
num = num + 1;
obj.num = obj.num + 1;
setState(state + 1);
};
return (
<div className="App">
<p>stateNum : {state}</p>
<p>obj.num : {obj.num}</p>
<p>local num : {num}</p>
<button onClick={addNumHandler}>클릭</button>
</div>
);
}
export default App;
위에 보면 state, obj, 변수 num 에 초깃값을 0 으로 주었다. 그리고 버튼을 클릭하면 각각 +1 을 하도록 했다.
그런데 화면에는 stateNum 만 +1 된 값이 출력 되고, 나머지 두개는 계속 0 만 나온다.
그 이유는 버튼을 눌러 state 값이 1씩 증가할때마다 변화를 감지하고 렌더링 되어 변수 값이 초기화되기 때문이다.

function App() {
const [state, setState] = useState(0);
const obj = { num: 0 };
let num = 0;
const refNum = useRef(0);
const addNumHandler = () => {
num += 1;
obj.num += 1;
refNum.current += 1;
setState(state + 1);
};
return (
<div className="App">
<p>stateNum : {state}</p>
<p>obj.num : {obj.num}</p>
<p>local num : {num}</p>
<p>useRef num : {refNum.current}</p>
<button onClick={addNumHandler}>클릭</button>
</div>
);
}
export default App;
이번에는 useRef 를 사용해 refNum.current 프로퍼티에 +1 하는 로직을 한 줄 추가했다.
그랬더니, state 가 변하여 컴포넌트가 렌더링 되어도 useRef 값은 최신값을 유지하고 있는 걸 확인할 수 있다.
그 이유는 useRef.current 프로퍼티에 값을 담고 있기 때문이다. 따라서 state 가 바뀌어 컴포넌트가 렌더링 되어도 useRef.current 의 값은 유지되고 있는 것이다.

위 내용이 모두 정답은 아니지만, 보통 이럴때 useRef 를 로컬 변수로 만들어 사용하면 매우 유용할 것 같다.
그리고 대표적으로는 setInterval 를 사용할때 반환하는 id 를 useRef 에 저장한 다음 clear 할때 많이 사용하는 듯하다.
리액트로 작업할때 div 등의 DOM 요소에 id, onClick 등을 적용하기도 한다. 이렇게 하면 특정 id 에 해당하는 DOM 요소에만 스타일을 따로 적용시키거나 onClick 등 이벤트리스너를 주어 해당 DOM 요소를 클릭하였을때 어떠한 행동이 일어나도록 설정할 수 있다.
이처럼 특정 DOM 에 접근해서 무언가를 해야할때도 useRef 로 간단하게 처리할 수 있다.
보통 input 창을 마우스로 클릭하지 않아도 바로 키보드로 입력할 수 있도록 useRef 를 이용해 focus 처리를 하는 예시가 가장 흔하다.
function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
나는 단순히 input 에 입력한 값을 가져올때 state 와 onChange 를 사용하지 않고, useRef 를 이용해 가져오는 방법을 주로 사용했다.
onChange, useState 로 input 에 입력한 값을 가져올때 콘솔에 찍어보면 ㅎ,ㅗ,ㅇ,ㄱ,ㅣ,ㄹ,ㄷ,ㅗ,ㅇ 이렇게 자음, 모음이 입력될 때마다 렌더링 되는거에 비해 useRef 를 사용하면 홍길동 한 단어만 콘솔에 찍히는 걸 확인 할 수 있다.
이렇게 하면 렌더링 수를 줄일 수 있고 input 창에 직접 접근하여 .current.value 를 통해 값을 가져올 수 있어서 편리하다. 별도의 onChange 함수 등을 작성할 필요가 없다.
이처럼 state 값이 바뀔 때마다 변화를 감지하고 그에 맞는 액션을 취해야 할때는 useState 를 사용해서 렌더링이 되도록 유도해야 하지만, 굳이 필요 없는 상황에서는 useRef 를 잘 활용하면 될 것 같다.
function App() {
const inputRef = useRef<HTMLInputElement | null>(null);
const onsubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const obj = {
name: inputRef.current?.value,
};
console.log(obj);
};
return (
<form className="App" style={{ padding: "1rem" }} onSubmit={onsubmit}>
<label htmlFor="name">이름</label>
<input type="text" ref={inputRef} name="name" />
</form>
);
}
export default App;

위에서 useRef 를 공부하고 다시 코드를 살펴보니 이제 이해가 가기 시작했다. useRef 의 DOM 요소에 직접 접근 할 수 있는 기능을 이용해서 useHover 훅을 만들었던 것이다.
const MyOrderSummary = () => {
const { ref, isHover } = useHover();
return (
<div>
.... 생략
<InformationBoxStyle ref={ref}>
<FcInfo />
{isHover && <span>예약 확인 중 입니다.</span>}
</InformationBoxStyle>
</div>
);
};
export default MyOrderSummary;
나는 icon 위에 hover 효과를 주고 싶어서 div 태그 안에 아이콘 <FcInfo> 을 넣어주고 div 태그에 ref 속성을 주어 mouseover 했을때 isHover 가 true 가 되어 '예약 확인 중 입니다.' 라는 문구가 나오도록 했다.
지금까지 useRef 를 사용하지 않았던 것도 아니고, input 에 ref 를 주어 값을 가져와 사용했던 방법과 사실상 별반 다르지 않았는데, useRef 에 대해 제대로 알고 쓰질 않았기 때문에 이번에 useHover 코드를 봤을때 작동방법에 대해 이해하지 못했던 것 같다.
뭐든지 핵심 역할을 제대로 이해하고 사용하는 것이 중요하다고 다시금 느끼게 되었다.
그리고 typescript 를 사용했기 때문에 useRef 를 쓰면서 애를 많이 먹었는데, type 공부도 더 많이 필요하다고 느꼈다. 특히 type 관련해서는 아래 글에서 정말 많은 도움을 받았다. 더 읽어보고 따라해보고 공부해봐야겠다.
https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5
도움 받은 글들
https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5
https://chanhuiseok.github.io/posts/react-7/