Written on
·
79
·
Edited
0
안녕하세요. 제가 직접 tailwind css v4를 이용해서 트위터 클론코딩을 진행하고 있는데
SearchForm에서 입력이 이루어지면, SearchForm의 영향으로 인해서 다른 영역에서도 재랜더링이 일어나는 것 같습니다.
먼저 묻고싶은 내용은 어떻게 하면 다른 영역은 재렌더링이 일어나지 않게 할까 입니다.
리엑트 개발자 도구로 렌더링이 일어날때 하이라이트 표시를 켰습니다. 이 때 SearchForm에 입력이 일어나면 위와같이 SearchForm만 아닌, 다른 영역에도 렌더링이 되는 모습이 보입니다.
SearchForm 구성은 다음과 같이했습니다.
최대한 현재 트위터의 입력 방식을 맞추어 보았습니다.
"use client";
import { useSearchParams } from "next/navigation";
import React, {
useRef,
useState,
ChangeEventHandler,
useEffect,
memo,
} from "react";
interface SearchFormProps {
q?: string;
}
const SearchForm = () => {
const [value, setValue] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const searchParams = useSearchParams();
const search = searchParams.get("q");
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) =>
setValue(e.target.value);
const handleClear = () => {
setValue("");
inputRef.current?.focus();
};
useEffect(() => {
setValue(search ?? "");
}, []);
return (
<div className="relative w-inherit h-12 group min-w-0">
{/* Border 레이어 */}
<div
className={`
absolute inset-0
border border-default-border
rounded-full
transition-all
duration-75
group-focus-within:border-2
group-focus-within:border-twiiter-blue
`}
/>
{/* 내부 요소 컨테이너 */}
<div className="relative w-full h-full">
<form
className="flex items-center w-full h-full px-4"
autoComplete="off"
onSubmit={(e) => e.preventDefault()}
>
{/* 검색 아이콘 (외부 div 기준 위치) */}
<svg
className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none"
width={20}
height={20}
viewBox="0 0 24 24"
aria-hidden="true"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
{/* Input */}
<input
ref={inputRef}
type="search"
placeholder="검색"
value={value}
onChange={handleChange}
className="
w-full h-full pl-10 pr-10 bg-transparent
text-base outline-none
placeholder-gray-500
appearance-none
"
/>
{/* Clear 버튼 (외부 div 기준 위치) */}
{value && (
<button
type="button"
onClick={handleClear}
className="
absolute right-3 top-1/2 -translate-y-1/2
w-7 h-7 flex items-center justify-center
bg-gray-700 hover:bg-gray-800
rounded-full
transition
"
tabIndex={-1}
aria-label="Clear"
>
<svg width={16} height={16} viewBox="0 0 20 20" fill="white">
<path d="M10 8.586l4.95-4.95 1.414 1.414L11.414 10l4.95 4.95-1.414 1.414L10 11.414l-4.95 4.95-1.414-1.414L8.586 10l-4.95-4.95L5.05 3.636 10 8.586z" />
</svg>
</button>
)}
</form>
</div>
</div>
);
};
export default SearchForm;
이를 SearchHeader에 적용하였으며.
import React from "react";
import SearchTab from "./SearchTab";
import SearchForm from "@/components/common/SearchForm";
const SearchHeader = () => {
return (
<div className="w-[calc(100%-72px)] sm:pl-0 sm:max-w-[598px] fixed backdrop-blur-lg border-b-1 border-default-border bg-white/70 dark:bg-black/30">
<div className="w-full mt-2">
<SearchForm />
</div>
<div className="w-full mt-1 h-[53px]">
<SearchTab />
</div>
</div>
);
};
export default SearchHeader;
최종적으로 /search 페이지에 이를 적용했습니다.
import React from "react";
import Post from "../home/_components/Post";
import SearchHeader from "./_components/SearchHeader";
interface SearchPageProps {
searchParams: Promise<{ q: string; f?: string; pf?: string }>;
}
const SearchPage = async ({ searchParams }: SearchPageProps) => {
const { q } = await searchParams;
return (
<main className="main-container">
<SearchHeader />
<div className="mt-[107px]">
<Post />
<Post />
<Post />
<Post />
<Post />
<Post />
<Post />
<Post />
<Post />
</div>
</main>
);
};
export default SearchPage;
관련된 layout.tsx는 다음과 같습니다.
import Image from "next/image";
import Link from "next/link";
import React, { ReactNode } from "react";
import logo from "@/../public/twitter-logo.svg";
import SearchForm from "@/components/common/SearchForm";
import NavMenu from "./_components/NavMenu";
import LinkButton from "@/components/common/LinkButton";
import FollowCard from "./_components/FollowCard";
import TrendCard from "./_components/TrendCard";
import RightSection from "./_components/RightSection";
interface AfterLoginLayoutProps {
children: ReactNode;
modal: ReactNode;
}
const AfterLoginLayout = ({ children, modal }: AfterLoginLayoutProps) => {
return (
<div className="overflow-y-scroll">
<div className="flex items-stretch">
<header className="flex items-end flex-col sm:flex-grow-1">
<section className="w-[72px] p-2 xl:p-0 xl:w-[275px] h-dvh">
<div className="flex flex-col w-inherit h-dvh fixed ">
<Link href={"/home"}>
<div className="w-14 h-14 rounded-[50%] flex justify-center items-center hover:bg-[rgba(15,20,25,0.1)]">
<Image
src={logo}
alt={"twitter logo"}
width={40}
height={40}
/>
</div>
</Link>
<NavMenu />
</div>
</section>
</header>
<div className="flex items-start flex-col h-dvh flex-grow-1">
<div className="h-full w-[100%] sm:w-[600px] lg:w-[920px] xl:w-[990px] flex justify-between ">
<main className="w-full sm:max-w-[600px] min-h-full h-fit border-l-1 border-r-1 border-default-border">
{children}
</main>
<div className="hidden lg:block lg:w-[290px] xl:w-[350px] h-fit relative">
<div className="flex flex-col w-inherit h-dvh fixed pt-1">
<RightSection />
</div>
</div>
</div>
</div>
</div>
{modal}
</div>
);
};
export default AfterLoginLayout;
SearchForm에 React.memo를 적용을 해보아도 변하지 않았습니다.
(다른 영역, 예를 들어 main이 아닌 양 옆의 영역들에 대해서도 React.memo를 넣었지만 그래도 렌더링이 일어나고 있습니다.)
재랜더링 조건은 부모 컴포넌트가 재랜더링 되거나, props가 바뀌거나, state 변경이 일어났을 때인걸로 알고 있습니다.
searchForm에서 Input만 영역에만 영향을 줘야할 것이 분명한데, 전체 영역에서 재랜더링이, 즉 layout.tsx에서 재랜더링이 일으키고 있는것 같습니다. (부모 영역)
혹시 렌더링이 어디까지 일어나는지 확인하는 (즉 최종 부모 컴포넌트까지) 방법이 있나요?
그리고 이런 문제가 있을때 어떻게 최적화 할 수 있나요?
p.s. 코드상 w-inherit 클래스가 이면 제가 직접 tailwind css utility에 추가하여 기능하도록 만들었습니다.
+ 추가
console.log()로 재랜더링이 일어나는지 확인해 보았는데 콘솔이 뜨지 않아 재랜더링이 아닌 다른 무슨 활성화(?) 인것 같아 보입니다... 하지만 SearchForm 입력 가지고 무언가 활성화 되는 것 자체가 조금 이해가 되지 않아서... 일단 재랜더링이라고 표현으로 남겨두었습니다...
Answer 2
1
저거 리렌더링 하이라이트가 정확한 게 아닙니다. 콘솔 로그로 찍었을 때 찍히나가 더 정확한 것이므로 지금은 리렌더링이 안 되고 있는 게 맞는 것 같네요.
0
안녕하세요, 인프런 AI 인턴입니다. 피곤한개발자님,
재랜더링 처리 및 최적화 관련 질문을 주셨군요. React에서 불필요한 재랜더링은 성능에 영향을 줄 수 있어 주의를 기울일 필요가 있습니다. 기본적으로 재랜더링이 발생하는 조건은 말씀하신 대로 부모 컴포넌트나 props, state의 변경입니다. 자세히 설명드리겠습니다.
React.memo와 useMemo 활용:
기본적으로 React.memo는 컴포넌트가 동일한 props를 받을 시 재랜더링을 방지합니다. 하지만 state가 변경되면 부모 컴포넌트의 재랜더링이 일어날 수 있으므로, 더 세분화된 상태 관리를 구현해야 합니다.
렌더링 범위 확인 방법:
render
함수에 로그를 삽입하여 해당 컴포넌트가 언제 렌더링 되는지를 확인할 수 있습니다.상태 관리 개선:
React.useCallback 사용:
handleChange
, handleClear
같은 함수는 useCallback
을 사용해 메모이제이션 해보세요.레이아웃 컴포넌트 구조 변경:
layout.tsx
에서 불필요한 부모 컴포넌트의 의존성이 생기지 않도록 필요한 자식 컴포넌트에서만 상태를 유지하세요.위 링크는 리렌더링과 관련된 React.memo, useMemo의 활용법과 경험 공유를 다룬 게시물입니다. 이를 통해 보다 구체적인 방법을 이해하고 적용해보실 수 있겠습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
개발자 도구인데도 부정확하다니 기묘하네요 ㅋㅋ 답변 감사합니다~