lodash 의 debounce
사용자의 반복 클릭을 막아야 하는 상황은 흔하게 발생하고, 기법으로는 쓰로틀링과 디바운싱이 대표적입니다.
이번에는 디바운싱(마지막 이벤트 이 후 특정시간 동안 이벤트가 발생하지 않을때 함수 호출)을 알아보려고 합니다.
대표적으로 lodash
라이브러리의 debounce
함수를 사용하면 디바운싱을 적용할 수 있습니다.
설치:
npm install lodash
예제 코드
debounce
를 사용하여 updateMemo
호출을 지연시키고, useEffect
를 통해 text
상태가 변경될 때만 렌더링하도록 합니다.
import { useState, useEffect, ChangeEvent } from "react";
import { useMemoApp } from "@/hooks/useMemoApp";
import { debounce } from "lodash";
import styled from "styled-components";
function Article() {
const { memos, selected, updateMemo } = useMemoApp();
const [text, setText] = useState("");
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
debounce((input: string) => {
updateMemo(input);
}, 500)
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const input = e.target.value;
setText(input);
debounce((input: string) => {
updateMemo(input);
}, 500) // 입력이 멈춘 후 500ms 후에 updateMemo 호출
};
const handleKeyUp = () => {
console.log("key up");
};
useEffect(() => {
const selectedMemo = memos.find((memo) => memo.id === selected);
if (selectedMemo) {
setText(selectedMemo.contents);
}
}, [memos, selected]);
useEffect(() => {
if (textAreaRef.current) textAreaRef.current.focus();
}, []);
return (
<StyledArticle>
<StyledSpan>{new Date().toLocaleString()}</StyledSpan>
<StyledTextArea
ref={textAreaRef}
onChange={handleChange}
onKeyUp={handleKeyUp}
value={text}
/>
</StyledArticle>
);
}
export default Article;
debounce 와 useRef 함께 사용
하지만 위 방식으로 lodash의 debounce 를 적용하는 과정에서 타이머가 계속 재설정되는 문제를 만났습니다.
이 문제를 해결하기 위해 useMemo, useRef 훅 중에서 고민하다가 useRef 가 더 적합한 방법이라고 판단했습니다.
useRef
를 사용하여 debounce
함수의 인스턴스를 유지하려고 한 이유는, 컴포넌트가 리렌더링될 때마다 새로운 debounce
함수가 생성되지 않도록 하기 위해서 입니다. debounce
함수는 호출될 때마다 새로운 타이머를 설정하므로, 컴포넌트가 리렌더링될 때마다 새로운 debounce
함수가 생성되면, 의도하지 않은 동작이 발생합니다.
useRef
를 사용하면 컴포넌트가 리렌더링되어도 debounce
함수의 인스턴스는 유지됩니다.
따라서 동일한 debounce
함수가 계속 사용되어, 입력이 멈춘 후 설정한 밀리세컨드 동안 새로운 입력이 없을 때만 updateMemo
가 호출됩니다.
이유 1: 동일한 함수 인스턴스 유지
const debouncedUpdateMemo = useRef(
debounce((input: string) => {
updateMemo(input);
}, 500)
).current;
이 코드는 debouncedUpdateMemo
가 컴포넌트의 생명주기 동안 동일한 debounce
함수 인스턴스를 유지하도록 합니다. useRef
는 컴포넌트가 리렌더링될 때도 변경되지 않는 값을 유지할 수 있습니다. 따라서 debouncedUpdateMemo
는 컴포넌트가 리렌더링되어도 동일한 debounce
함수 인스턴스를 가리키게 됩니다.
이유 2: 불필요한 리렌더링 방지
만약 debounce
함수를 useEffect
나 다른 방식으로 매번 생성한다면, 컴포넌트가 리렌더링될 때마다 새로운 debounce
함수가 생성되어 불필요한 타이머가 설정되고, 이전 타이머는 무효화될 수 있습니다. 이를 방지하기 위해 useRef
를 사용하여 동일한 debounce
함수 인스턴스를 유지합니다.
코드 예시
import { useState, useEffect, useRef, ChangeEvent } from "react";
import { useMemoApp } from "@/hooks/useMemoApp";
import { debounce } from "lodash";
import styled from "styled-components";
function Article() {
const { memos, selected, updateMemo } = useMemoApp();
const [text, setText] = useState("");
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
// useRef를 사용하여 debounce 함수 인스턴스를 유지
const debouncedUpdateMemo = useRef(
debounce((input: string) => {
updateMemo(input);
}, 500)
).current;
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const input = e.target.value;
setText(input);
debouncedUpdateMemo(input); // 입력이 멈춘 후 500ms 후에 updateMemo 호출
};
const handleKeyUp = () => {
console.log("key up");
};
useEffect(() => {
const selectedMemo = memos.find((memo) => memo.id === selected);
if (selectedMemo) {
setText(selectedMemo.contents);
}
}, [memos, selected]);
useEffect(() => {
if (textAreaRef.current) textAreaRef.current.focus();
}, []);
return (
<StyledArticle>
<StyledSpan>{new Date().toLocaleString()}</StyledSpan>
<StyledTextArea
ref={textAreaRef}
onChange={handleChange}
onKeyUp={handleKeyUp}
value={text}
/>
</StyledArticle>
);
}
export default Article;
const StyledArticle = styled.article`
display: flex;
flex-direction: column;
padding: 20px;
width: 100%;
`;
const StyledSpan = styled.span`
color: rgb(128, 128, 128);
font-size: 10px;
margin: 0px auto 24px;
`;
const StyledTextArea = styled.textarea`
all: unset;
flex-grow: 1;
font-size: 15px;
line-height: 1.66;
`;
요약
useRef
를 사용하여debounce
함수의 인스턴스를 컴포넌트 생명주기 동안 유지합니다.- 이를 통해 컴포넌트가 리렌더링될 때마다 새로운
debounce
함수가 생성되지 않도록 합니다. - 이는 불필요한 타이머 생성과 무효화를 방지하고, 의도한 대로
debounce
가 작동하도록 보장합니다.
'react' 카테고리의 다른 글
[240610 TIL] Dom Purify, html-react-parser (0) | 2024.06.10 |
---|---|
[240607 TIL] Quill base64 이미지 처리 (1) | 2024.06.07 |
[240525 WIL] Context API, RTK 정리 (0) | 2024.05.26 |
[240526 TIL] Link state (0) | 2024.05.26 |
[240522 TIL] Context API (0) | 2024.05.22 |