/* .babelrc */
...
"plugins": [
["inline-react-svg"],
[
"module-resolver",
{
"root": ["."],
"alias": {
"@pages": "./src/pages/m",
"@components": "./src/components",
"@layouts": "./src/layouts",
"@store": "./src/store"
}
}
...
/* tsconfig.json */
"paths": {
"@pages/*": ["src/pages/m/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"]
}
/* next.config.js */
module.exports = {
resolve: {
alias: {
'@pages': path.resolve(__dirname, 'src/pages/m'),
'@components': path.resolve(__dirname, 'src/components'),
'@layouts': path.resolve(__dirname, 'src/layouts'),
},
},
};
이전에 어드민 페이지를 개발할 때 정리한 글(Next.js+Redux-Toolkit)을 보고 샘플 코드만 작성하였다.
/* Dropdown.tsx */
import { useState, useEffect, useCallback, useRef } from 'react';
/* props로 받아야하는 것: 옵션 리스트, onChangeHandler 함수 */
// 인터페이스로 props 선언해주었다.
interface Props {
itemList: Array<any>;
placeholder?: string;
onChange(value: string);
}
/* Exchange.tsx*/
import { useState, useMemo, useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import Image from 'next/image';
import Dropdown from '@components/Common/Dropdown';
import { MarketList } from 'src/lib/MarketList'; // 드랍다운에 들어갈 목록 모음
const ExchangeComponent = () => {
// 배열은 두 종류로 나뉘어서 두 개의 드랍다운 컴포넌트에 들어가게 된다.
const mainMarketList = MarketList.filter(
(market) => !market.isComparedMarket,
);
const comparedMarketList = MarketList.filter(
(market) => market.isComparedMarket,
);}
// 두 개의 드랍다운 컴포넌트에서 선택된 값을 관리해야하기때문에 useState를 사용한다.
const [currentMarket, setCurrentMarket] = useState<string>(
(market as string) || mainMarketList[0].id,
);
const [currentComparedMarket, setCurrentComparedMarket] = useState<
string | null
>();
// 드랍다운 목록에서 값을 선택하고 저장하는 함수를 선언해준다.
const changeMarket = useCallback(
(value: string) => {
setCurrentMarket(value);
},
[currentMarket],
);
const changeComparedMarket = (value: string) => {
setCurrentComparedMarket(value);
};
return (<>
<div className="select group">
<Dropdown
onChange={changeMarket}
itemList={mainMarketList}
placeholder="거래소 선택"
/>
<Image src="/svg/exchangeCompare.svg" width={13} height={16} />
<Dropdown
onChange={changeComparedMarket}
placeholder="비교할 거래소 선택"
itemList={comparedMarketList}
/>
</div></>)
data-id
라는 속성을 이용했다.id
를 사용하지 않았냐면... 이미 id
속성은 드랍다운 컴포넌트에서 선택된 값을 찾기 위해서 사용했기때문에 중복으로 사용하기엔 다소 번거로웠기때문이다.그래서 찾아보다가 어떤 값이든 담아둘 수 있는 data-
속성에 대해 알게 되었고 활용하게 되었다.
onSelectItem
함수가 실행될 때
1. 클릭된 노드를 확인한다.
2-1. 클릭된 노드가 목록 아이템의 자식 노드일 경우(이미지 또는 텍스트)에는 부모 노드에서dataset.id
를 찾는다.
2-2. 클릭된 노드가 목록 아이템 노드 자신일 경우 바로dataset.id
를 찾는다.
3.dataset.id
를value
변수에 담아 props로 받은onChange
함수에 실행한다.
4. 부모 컴포넌트에서 state가 변경된다.
// 코드가 길어짐에 따라 부모 컴포넌트와 연관된 부분만 작성한다.
/* Dropdown.tsx */
const Dropdown = (props: Props) => {
/* props 로 받는 onChange가 바로 셀렉트 값을 관리하는 함수다. */
const { itemList, placeholder, onChange } = props;
/* 드랍다운 열린 상태/닫힌 상태 (true/false) */
const [isActive, setIsActive] = useState<boolean>(false);
const [item, setItem] = useState<any>(null);
/* item의 타입은 HTMLElement로, 사실상 부모 컴포넌트와는 관계없이 오로지 화면에 보여지는 부분만 담당한다.*/
const defaultSelect = useRef<HTMLDivElement>(null);
const onActiveToggle = useCallback(() => {
setIsActive((prev) => !prev);
}, []);
/* */
const onSelectItem = useCallback((e) => {
const targetId = e.target.id;
if (targetId === 'item_name') {
const value = e.target.parentElement.dataset.id;
setItem(e.target.parentElement.innerHTML);
onChange(value);
} else if (targetId === 'item') {
const value = e.target.dataset.id;
setItem(e.target.innerHTML);
onChange(value);
}
setIsActive((prev) => !prev);
}, []);
return (
/* 드랍다운 목록 부분 */
// 목록 아이템이 img라는 키를 포함하는 경우
{Object.keys(itemList[0]).includes('img')
? itemList.map((el, index) => {
return (
<div
id="item"
data-id={el.id}
className="item with-icon"
onClick={onSelectItem}
>
<img
src={el.img}
id="item_name"
width={18}
height={18}
style={{ marginRight: '0.5rem' }}
/>
<span id="item_name">{el.name}</span>
</div>
);
})
// 목록 아이템에 이미지가 없고 텍스트만 있는 경우
: itemList.map((el, index) => {
return (
<div
data-id={el.id}
className="item"
onClick={onSelectItem}
id="item"
>
<span id="item_name">{el.name}</span>
</div>
);
})}
)
}
참고자료
https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/Use_data_attributes
background-image
속성을 사용하여 active
상태와 non-active
상태일 때의 거래소 필터 디자인을 적용해주었다.
& > #bithumb {
background-image: url('/png/exchange/logo/bithumb.png');
width: 5.5rem; // 로고 이미지를 감싸고 있는 div의 너비
background-size: 3.625rem 0.875rem; // background-image의 사이즈(가로 세로)
-webkit-background-size: 3.625rem 0.875rem;
-moz-background-size: 3.625rem 0.875rem;
}
& > #bithumb.active {
background-image: url('/png/exchange/logo/white/bithumb.png');
}