[리액트] 특정 문자열만 색상을 바꿔서 랜더링해보자(feat.검색창)
이번 포스트에서는 검색 컴포넌트에서 매칭되는 문자열만 색상을 칠하는 방법에 대해 한번 알아보겠습니다.
이 기능은 보시다시피 검색창에 있어선 나름 필수적인 기능이라고 생각합니다.
저는 안드로이드 개발할때 같은 기능을 만들어 본 경험을 있으니 이번에도 같은 방식으로 구현해보도록 하겠습니다.
[ 결과 ]
[구현 시작]
아이디어는 크게 2가지 방법을 생각해봤습니다.
- 특정 검색어(이하 쿼리)가 발견되면 해당 텍스트의 색상만 바꿔주는 기능을 추가한다.
- input에 입력된 쿼리를 css로 불려와 주식검색창에 있는 텍스트가 같은 경우 색상을 변경한다.
안드로이드에서는 1번 방식으로 구현했고 이번에도 1번 방식으로 구현을 합니다.
다만 2번 방법은 제가 요새 css를 공부하고 있는데 input[value]로 왠지 구현할 수 있지 않을까 생각만 하고 있어서 추후에 바뀌게 된다면 내용를 추가해보도록 하겠습니다.
일단 다시 돌아가서 주식 검색 컴포넌트의 동작 방식은 다음과 같습니다.
- 쿼리를 DB에서 조회해서 매칭 데이터를 가져온다.
- 해당 데이터들을 가져와 주식 검색창 컴포넌트의 body에 뿌려준다.
따라서 우리가 해야할 것은 2번 과정에서 쿼리와 매칭되는 단어에 색상을 넣어주면 되는 간단한 일입니다.
{matchedStocks.map((StockData: { code: string; name: string; checked: boolean }) => (
<div>
<div>{StockData.code}</div>
<div>{StockData.name}</div>
<Checkbox/>
</div>
))}
위에서 데이터를 변경하는 코드는 전부 삭제하고 랜더링하는 부분만 보도록 하겠습니다.
matchedStocks 배열은 code, name, checked를 가진 객체배열입니다.
이제 이 {StockData.code}, {StockData.name} 를 좀 더 상세히 바꿔서 구현을 해보겠습니다.
//searchQuery <- 입력한 검색어
{matchedStocks.map((StockData: { code: string; name: string; checked: boolean }) => (
<div>
<div>
{StockData.code.includes(searchQuery) ? (
<>
{StockData.code.split(searchQuery)[0]}
<span style={{ color: "#3F51B5" }}>{searchQuery}</span>
{StockData.code.split(searchQuery)[1]}
</>
) : (
StockData.code
)}
</div>
<div>
{StockData.name.includes(searchQuery) ? (
<>
{StockData.name.split(searchQuery)[0]}
<span style={{ color: "#3F51B5" }}>{searchQuery}</span>
{StockData.name.split(searchQuery)[1]}
</>
) : (
StockData.name
)}
</div>
<Checkbox />
</div>
));}
로직은 간단합니다. 쿼리가 있으면 해당 문자열을 잘라서 맞는 부분만 색을 칠하고 넣어주는 로직입니다.
[세부 구현 확인]
해당 로직을 좀더 살펴보도록 합시다.
주식 이름 "삼성전자"와 쿼리 "전"으로 한번 보겠습니다.
let stockName= "삼성전자";
let searchQuery= "전";
stockName.split(searchQuery); //["삼성", "자"]
stockName.split(searchQuery)[0]+searchQuery+stockName.split(searchQuery)[1]; //"삼성전자"
보시다시피 split 한 뒤에 앞뒤로 값을 합치면 됩니다. 따라서 우리는 searchQuery에만 특정 색상을 주입시키면 되는 것입니다.
{StockData.name.includes(searchQuery) ? (
<>
{StockData.name.split(searchQuery)[0]}
<span style={{ color: "#3F51B5" }}>{searchQuery}</span>
{StockData.name.split(searchQuery)[1]}
</>
) : (
StockData.name
)}
따라서 줄바꿈이 없는 span으로 searchQuery를 감싸주고 해당 style에 원하는 색상값을 넣고 랜더링 하면 완성입니다.
근데 보시면 코드에 중복이 있고 직관적이지 않으니 리펙토링을 해보도록 합시다.
[ 리펙토링 ]
리펙토링 방법에는 2가지가 있습니다.
- 해당 로직을 별도의 컴포넌트로 뺀다.
- 쿼리가 매칭되는 것만 진짜로 바꿔끼운다.
1번 방법 - 해당 로직을 별도의 컴포넌트로 뺀다.
const ColoredItem = ({ item, query }: { item: string; query: string }) => {
return item.includes(query) ? (
<>
{item.split(query)[0]}
<span style={{ color: "#3F51B5" }}>{query}</span>
{item.split(query)[1]}
</>
) : (
<>{item}</>
);
};
// 사용
<ColoredItem item={StockData.code} query={searchQuery} />;
2번 방법 - 쿼리가 매칭되는 것만 진짜로 바꿔끼운다.
리액트에는 dangerouslySetInnerHTML라는 속성이 있습니다. 이름부터 위험한 기존의 브라우저 DOM에서 innerHTML를 대체하는 기능입니다.
하지만 일반적으로 코드상에서 html를 설정하는 행위는 XSS 위험성이 있어서 권고되는 방법이 아니라고 합니다.
그래도 일단 있으니 한번 써보도록 하겠습니다.
기존 코드를 전부 날리고 자바스크립트의 replaceAll로 쿼리를 원하는 색상을 입힌 스트링으로 바꿔줍니다.
// 기존
<div className={classes.stockName} id={`${StockData.name}_name`}>
{StockData.name.includes(searchQuery) ? (
<>
{StockData.name.split(searchQuery)[0]}
<span style={{ color: "#3F51B5" }}>{searchQuery}</span>
{StockData.name.split(searchQuery)[1]}
</>
) : (
StockData.name
)}
</div>;
//수정
let coloredCode = StockData.code.replaceAll(searchQuery, `<span style="color: #3F51B5;">${searchQuery}</span>`);
<div className={classes.stockCode} id={`${StockData.code}_code`}>
<div dangerouslySetInnerHTML={{ __html: coloredCode }}></div>
</div>;
다만 이젠 style 안에 들어가는 코드를 html형식으로 작성해주셔야 합니다. jsx 형식으로 작성하면 안됩니다.
끝.