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 |