2022/01/13

devPomme·2022년 1월 13일
0

21REBIRTH

목록 보기
6/10
post-custom-banner

오늘 한 일

tsconfig-paths(alias) 설정

/* .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'),
    },
  },
};

redux-wrapper 세팅

이전에 어드민 페이지를 개발할 때 정리한 글(Next.js+Redux-Toolkit)을 보고 샘플 코드만 작성하였다.

드랍다운 컴포넌트 props 설정

드랍다운 컴포넌트에서 인터페이스 사용하기

/* Dropdown.tsx */

import { useState, useEffect, useCallback, useRef } from 'react';
/* props로 받아야하는 것: 옵션 리스트, onChangeHandler 함수  */
// 인터페이스로 props 선언해주었다. 
interface Props {
  itemList: Array<any>;
  placeholder?: string;
  onChange(value: string);
}

부모 컴포넌트에서 드랍다운 컴포넌트에서 사용할 props 선언 및 내려주기

/* 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></>)

드랍다운 컴포넌트에서 props로 받은 핸들러 함수 사용하기

  • 나는 목록 배열 요소의 id를 부모 컴포넌트에 value 로 넘겨주기 위해서 data-id 라는 속성을 이용했다.
  • id를 사용하지 않았냐면... 이미 id 속성은 드랍다운 컴포넌트에서 선택된 값을 찾기 위해서 사용했기때문에 중복으로 사용하기엔 다소 번거로웠기때문이다.

그래서 찾아보다가 어떤 값이든 담아둘 수 있는 data- 속성에 대해 알게 되었고 활용하게 되었다.

onSelectItem 함수가 실행될 때
1. 클릭된 노드를 확인한다.
2-1. 클릭된 노드가 목록 아이템의 자식 노드일 경우(이미지 또는 텍스트)에는 부모 노드에서 dataset.id를 찾는다.
2-2. 클릭된 노드가 목록 아이템 노드 자신일 경우 바로 dataset.id 를 찾는다.
3. dataset.idvalue 변수에 담아 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');
      }

profile
헌신하고 확장하는 삶
post-custom-banner

0개의 댓글