사용자가 검색한 키워드를 하이라이팅하여 시각적으로 강조하는 기능은 사용자 경험을 크게 향상시킬 수 있습니다. 부트캠프 파이널프로젝트 진행 당시 구현 경험을 작성합니다.
우선, 우리가 다룰 컴포넌트 구조를 간단히 살펴보겠습니다.
ComplainsContent 컴포넌트 : 검색 결과를 렌더링하고, 각 결과 항목을 ComplainItem 컴포넌트로 전달합니다.
ComplainItem 컴포넌트 : 개별 항목의 데이터를 표시하며, ComplainsContent로부터 전달된 검색 키워드를 사용하여 제목을 하이라이팅합니다.
ComplainsContent 컴포넌트는 여러 개의 게시글을 검색하고 그 결과를 리스트 형태로 보여줍니다. 여기서 핵심은 사용자가 검색한 키워드를 포함한 게시글 제목을 강조하는 것입니다.
아래는 ComplainsContent 컴포넌트의 일부 코드로, 검색 키워드를 강조하는 기능을 보여줍니다.
const highlightKeyword = (title: string, keyword: string) => {
if (!keyword) return title; // 키워드가 없으면 원래 제목을 반환
const parts = title.split(new RegExp(`(${keyword})`, "gi")); // 키워드를 기준으로 제목을 분할
return (
<>
{parts.map((part, index) => (
<span
key={index}
style={
part.toLowerCase() === keyword.toLowerCase()
? { color: "red" } // 키워드와 일치하는 부분은 빨간색으로 강조
: {}
}
>
{part}
</span>
))}
</>
);
};
이 함수는 두 개의 인자를 받습니다. 첫 번째는 title (게시글 제목)이고, 두 번째는 keyword (사용자가 검색한 키워드)입니다.
<span>
태그로 감싸고 빨간색으로 스타일링하여 강조합니다. 나머지 텍스트는 기본 스타일로 렌더링합니다.ComplainsContent 컴포넌트는 검색 결과 목록을 렌더링하기 위해 ComplainItem 컴포넌트를 사용합니다. 여기서 highlightKeyword 함수를 호출하여 각 게시글 제목을 하이라이팅 처리한 후, 해당 결과를 title 프롭스로 ComplainItem 컴포넌트에 전달합니다.
{responseData.posts.content.map((item) => (
<ComplainItem
key={item.id}
id={item.id}
user_nickname={item.user_nickname}
status={item.status}
category_name={item.category_name}
title={highlightKeyword(item.title, keyword)} // 하이라이트 된 제목 전달
image_urls={item.image_urls}
visible={item.visible}
count_reaction_type_good={
item.reaction_columns
? item.reaction_columns.count_reaction_type_good
: 0
}
count_of_comments={item.count_of_comments}
hits={item.hits}
created_at={item.created_at}
ishot={item.hot}
isnew={item.new}
/>
))}
ComplainItem 컴포넌트는 ComplainsContent 컴포넌트로부터 받은 프롭스를 사용하여 게시글 항목을 렌더링합니다. 여기서 하이라이팅된 제목은 이미 highlightKeyword 함수에 의해 <span>
태그와 스타일링이 적용된 상태입니다. 따라서 ComplainItem에서는 추가적인 처리 없이 그대로 제목을 렌더링할 수 있습니다.
import Link from "next/link";
type FreeBoardItemProps = {
id: number;
status: string;
user_nickname: string;
category_name: string;
title: string | React.ReactNode; // 제목은 문자열 또는 리액트 노드로 받을 수 있음
image_urls?: string[] | null;
visible: boolean;
count_reaction_type_good: number;
count_of_comments: string;
hits: number;
created_at: string;
ishot: boolean;
isnew: boolean;
};
export default function ComplainItem({
id,
status,
user_nickname,
category_name,
title, // 프롭스로 전달받은 하이라이트된 제목
image_urls,
visible,
count_reaction_type_good,
count_of_comments,
hits,
created_at,
ishot,
isnew,
}: FreeBoardItemProps) {
const handleClick = () => {
if (!visible) {
alert("비밀글입니다.");
}
};
const convertDate = (dateString: string) => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}.${month}.${day}`;
};
const convertedDate = convertDate(created_at);
return (
<tr className="text-center border-b border-grey_200">
<td className="px-4 py-7 text-grey_300">
{status === "RECEIVED" ? "접수" : status === "IN_PROGRESS" ? "처리중" : "처리완료"}
</td>
<td className="px-4 py-7 text-grey_300">{category_name}</td>
<td className="text-left text-grey_900 font-medium">
<div className="flex items-center">
<Link
className="mr-2"
href={visible ? `/complains/${id}` : "#"}
onClick={handleClick}
>
<p className="font-medium">{title}</p> {/* 하이라이트된 제목 사용 */}
</Link>
</div>
</td>
<td className="px-4 py-7 text-grey_900">{user_nickname}</td>
<td className="px-4 py-7 text-grey_900">{count_reaction_type_good}</td>
<td className="px-4 py-7 text-grey_900">{hits}</td>
<td className="px-4 py-7 text-grey_900">{convertedDate}</td>
</tr>
);
}
이 예제에서 ComplainsContent 컴포넌트는 전체 게시글 데이터를 관리하며, 검색 키워드에 따른 하이라이팅 로직도 처리합니다. 그 후, 개별 게시글 항목을 렌더링하기 위해 ComplainItem 컴포넌트를 사용합니다. title 프롭스로 하이라이팅된 제목을 ComplainItem에 전달함으로써, ComplainItem은 이 데이터를 단순히 출력하기만 하면 됩니다.