[Tia 기업협업] Tailwind CSS 활용

front-end developer·2022년 11월 3일
0

Antd CSS Framework

1. 테이블 솔팅 기능

클릭 시 생성되는 이미지 초기화 하기

솔팅기능은 표의 카테고리 탭이 클릭되면 클릭된 카테고리와 오름차순 혹은 내림차순의 정보를 담아 API에 GET 요청을 넣고, 반환하는 데이터를 받아서 보여주는 방식으로 구현했다.

추가적으로, 솔팅이 이뤄진 메뉴에 오름차순 혹은 내림차순이 적용되었다는 이미지를 나타내는 css를 넣어야했다. 솔팅이 이뤄지면 css를 적용하기 위해 className을 동적으로 활용했다. sorting sorting_asc 혹은 sorting sorting_desc 의 값을 className에 할당하고 이 className에 ::after css 속성을 부여해 css 적용까지 완료한 상태였다.

const sortData = (target) => {
    const isContained = (value) => {
      return target.className.includes(value);
    };
    const isFirstClick = isContained('sorting');
    const isDesc = isContained('sorting_desc');

    if (!isFirstClick || isDesc) {
      setMenuClassName(`sorting sorting_asc`);
      fetchData(target.headers, 'asc');
    }
    else {
      setMenuClassName(`sorting sorting_desc`);
      fetchData(target.headers, 'desc');
    }
  };
.sorting_asc {
  &::after {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    content: '';
    background-size: cover;
    background-image: url(https://cdn-icons-png.flaticon.com/512/25/25678.png);
  }
}
.sorting_desc {
  &::after {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    content: '';
    background-size: cover;
    background-image: url(https://cdn-icons-png.flaticon.com/512/25/25243.png);
  }

그러나, 문제는 솔팅버튼 이미지가 생겨난 이후 다른 메뉴를 클릭했을 때 기존 적용되었었던 솔팅에 css초기화가 이뤄지지 않았다. 즉, 다 한번씩 솔팅이 일어나면 모든 메뉴에 솔팅버튼 이미지가 부여되어 버린다. 어떻게 하면 하나의 메뉴탭에만 css를 적용할 수 있을까 고민을 했다. 구현할 수 있는 방식이 떠올랐지만, 내가 생각하기에도 주먹구구식의 방법이라 더 좋은 방법을 고민하고 있었다.

이 Blocker를 팀원과의 daily standup meeting에서 공유하였고 금관님의 도움으로 해결책을 찾을 수 있었다. 클래스 네임을 동적으로 활용하면서 동시에 조건부 렌더링을 부여하는 방법이다.

// index.jsx
const UserList = () => {
const [onSort, setOnSort] = useState(null);

retun (
{MENU_DATA.map((menu) => {
                  return (
                    <th
                      key={menu.id}
                      className={`min-w-${menu.width}px ${
                        onSort === menu.id && menuClassName
                      }`}
                      headers={menu.name}
                      onClick={(e) => {
                        setOnSort(menu.id);
                        sortData(e.target);
                      }}
                    >
                      {menu.title}
                    </th>
                  );
                })}
	)
}

특정 메뉴탭에 클릭이 발생하면 먼저 onSort state에 특정 id(숫자)가 부여된다. 이 id가 해당 메뉴탭의 id정보와 일치하게 될 때만 className이 적용되도록 구현했다. 결국, 솔팅이 작동하고 있는 메뉴탭에만 클래스 네임이 부여되고, 그 외 나머지 메뉴탭은 조건부렌더링에서 조건에 탈락되어 클래스 네임이 부여가 되지 않는다.

⇒ 이후 수정 사항
antd의 Table 컴포넌트를 사용하게 되면서 솔팅 기능은 매우 간략해졌다. antd의 Table 컴포넌트에 이미 sorting하는 기능이 어느정도 구현이 되어있기 때문에, 로직만 살짝 변경해주면 간단하게 기능 구현이 가능했다.

// src/components/table/ListTable.js

import { Table } from 'antd';

const ListTable = ({
}) => {
  const sortData = (target) => {
    const orderType = target.columnKey;
    const sortType = target.order.slice(0, -3);
    fetchData(orderType, sortType);
  };

  return (
    <>
      <Table
        // 중략
        onChange={(rowkeys, rows, info) => {
          sortData(info);
        }}
      />
    </>
  );
};
  • Table은 antd의 컴포넌트로 상단에 import 하여 사용.
    • onChange 속성은 antd의 API에 따라 rowkeys, rows, info 의 인자를 가질 수 있으며, sorting 기능에 사용할 값은 info 이다.
    • info 를 콘솔에 찍어보면 다음과 같은 결과가 나온다.
    • 위의 값 중 columnKey, order 값을 사용 → 서버에 Params로 전달한다.

2. 검색창 날짜 연동 기능

특정기간을 나타내는 radio를 누르면 달력에 값 연동하기

검색창에 기간을 입력하기 위해 달력을 띄우는 건 antd의 DatePicker 컴포넌트를 import해 구현할 수 있었다. 그 다음 구현 사항은 radio버튼(7일, 30일, 90일)을 클릭하였을 때, 해당 기간이 달력에 표시되어야 하는 기능이었다.

이 달력 기능의 구조는 다음과 같다.

			<RangePicker
				//중략
      />
      <Radio.Group
				//중략
      >
        <Radio style={{ color: '#7E8299' }} value={7}>
          7</Radio>
        <Radio style={{ color: '#7E8299' }} value={30}>
          30</Radio>
        <Radio style={{ color: '#7E8299' }} value={90}>
          90</Radio>
      </Radio.Group>

RangePickerRadio 모두 antd 컴포넌트를 활용했다. RangePicker API를 찾아본 결과 컴포넌트에 value라는 속성을 부여하고 그 값으로 Radio 버튼에 의해 부여된 값을 연동시키면 되겠다라고 생각했다.

현재 전체적인 로직은 검색창에서 입력된 값들이 filter라는 객체에 담기고 이 filter객체의 정보를 검색 이벤트가 발생하면 백엔드 API에 보내는 로직이다. 따라서, Radio에 버튼이 클릭되면 시작일과 종료일을 계산해 setState를 통해 filter객체에 담고, 달력 컴포넌트에서는 value 값으로 이 필터에 담긴 시작,종료일 정보를 부여하면 연동이 될 것 같았다.

그런데, RangePicker에서 요구하는 value속성은 moment()형태였고 백엔드 API에 보내기로한 약속된 형식은 moment()형태에서 변환과정을 거친 moment().format('YYYY-MM-DD') 의 형태였기 때문에 value값으로 filter의 시작 종료일을 부여하면 “date1.isAfter is not a function” 이라는 에러 문구가 나왔다.

결국, RangePicker에 value 값으로 부여하기 위한 date state를 하나 생성해 원하는 기능을 구현하였다.

// DataSearch.js

const DateSearch = ({ date, setDate, selectedDays, setSelectedDays, page }) => {
  const { RangePicker } = DatePicker;
  const dispatch = useDispatch();
  const addToFilter = (key, value, page) => {
    switch (page) {
      case 'userinfo':
        dispatch(addUserFilter(key, value));
        break;

      case 'preuser':
        dispatch(addPreUserFilter(key, value));
        break;

      case 'rankuser':
        dispatch(addRankUserFilter(key, value));
        break;

      default:
        break;
    }
  };

  const changeDateForm = (target) => {
    return `${target._d.getFullYear()}-${
      '0' + (target._d.getMonth() + 1)
    }-${target._d.getDate()}`;
  };

  return (
    <>
      <RangePicker
        locale={locale}
        value={[date.start, date.end]}
        style={{ width: '26.5%' }}
        onChange={(e) => {
          setDate({ start: e[0], end: e[1] });
          addToFilter('start_at', changeDateForm(e[0]), page);
          addToFilter('end_at', changeDateForm(e[1]), page);
        }}
      />
      <Radio.Group
        style={{ marginLeft: '20px' }}
        value={selectedDays}
        onChange={(e) => {
          const howManyDays = e.target.value;
          setSelectedDays(howManyDays);
          setDate({
            start: moment().day(-howManyDays),
            end: moment(),
          });
          addToFilter(
            'start_at',
            moment().day(-howManyDays).format('YYYY-MM-DD'),
            page
          );
          addToFilter('end_at', moment().format('YYYY-MM-DD'), page);
        }}
      >
        <Radio style={{ color: '#7E8299' }} value={7}>
          7</Radio>
        <Radio style={{ color: '#7E8299' }} value={30}>
          30</Radio>
        <Radio style={{ color: '#7E8299' }} value={90}>
          90</Radio>
      </Radio.Group>
    </>
  );
};

export default DateSearch;

⇒ 이후 수정 사항

기존

  • 서버에 보내기 위한 날짜형식 → filter 객체에 가공후 저장
  • 현재 값을 나타내기 위해, value 속성에 부여하는 날짜 형식 (가공 필요 x)

수정 이후

  • 서버에 보내기 위한 날짜형식 → filter 객체에 가공하지 않고 다른 이름으로 저장
    • filter 객체에 start, end 키 값으로 저장 후 사용
  • 현재 값을 나타내기 위해, value 속성에 부여하는 날짜 형식 (가공 필요 x)
    • filter 객체에 start_at, end_at 키 값으로 저장 후 사용

추가로 Radio 버튼이 나타내는 날짜 (7일, 30일, 90일)을 나타내는 howManyDays 변수를 기존 state에 저장하는 것에서 filter 객체에 저장하는 것으로 변경.

⇒ filter 객체는 store에서 관리되는 값이기 때문

return (
    <>
      <RangePicker
        locale={locale}
        value={[filters.start, filters.end]}
        style={{ width: '26.5%' }}
        onChange={(e) => {
          addToFilter('start_at', changeDateForm(e[0]), page);
          addToFilter('end_at', changeDateForm(e[1]), page);
        }}
      />
      <Radio.Group
        style={{ marginLeft: '20px' }}
        value={filters.selectedDays}
        onChange={(e) => {
          const howManyDays = e.target.value;
          addToFilter(
            'start_at',
            moment().day(-howManyDays).format('YYYY-MM-DD'),
            page
          );
          addToFilter('end_at', moment().format('YYYY-MM-DD'), page);
          addToFilter('start', moment().day(-howManyDays), page);
          addToFilter('end', moment(), page);
          addToFilter('selectedDays', howManyDays, page);
        }}
      >
        <Radio style={{ color: '#7E8299' }} value={7}>
          7</Radio>
        <Radio style={{ color: '#7E8299' }} value={30}>
          30</Radio>
        <Radio style={{ color: '#7E8299' }} value={90}>
          90</Radio>
      </Radio.Group>
    </>
  );

3. ant Table value 값 오류

Input 태그에 value를 적용시켜도 값이 적용이 안되는 문제

html의 input 태그는 value 속성에 state값을 부여하고, onChange 속성에서 이 state를 업데이트 한다면, state값을 초기화 시켰을 때 자동으로 input안에 값을 초기화 시킬 수 있다.

SEARCH_DATA.slice(0, count).forEach((input) => {
      children.push(
        <Col span={6} key={input.id}>
          <Form.Item name={input.name} label={input.title}>
            {input.type === 'input' ? (
              <Input
                name={input.name}
								value={filers.name}
                onChange={(e) => {
                  setFilters({ ...filters, [e.target.name]: e.target.value });
                }}
              />
						// 중략
          </Form.Item>
        </Col>
      );
    });

여기서, antd의 Input 컴포넌트는 위와 같이 코드를 구현했을 때, 초기화가 이뤄지지 않았다. 그 이유를 찾기위해 antd 공식문서를 살펴보니 Input 컴포넌트 API에 다음과 같이 나와있었다.

value 속성에는 string값을 줘야한다는 부분이 걸려서, 문제가 여기에 있지 않을까란 생각을 했다. 그리고 구글링으로 나와 같은 사례가 있는지 검색을 해봤는데 stackoverflow 사이트에서 Form 컴포넌트의 fields 속성을 활용해야 한다는 글을 보고 공식문서를 찾아봤다.

Form 컴포넌트의 fields 속성은 form 영역(어디까지 의미하는 지는 모르겠으나, form 태그 안의 영역이라고 생각)의 state management를 control한다고 나와있었다. 이 속성을 통해 form태그 하위 input 태그의 value state를 컨트롤 할 수 있다고 생각해 예시를 참고해 다음과 같이 부여했다.

const fields = [
    { name: ['level'], value: filters.level },
    { name: ['name'], value: filters.name },
    { name: ['nickname'], value: filters.nickname },
    { name: ['email'], value: filters.email },
    { name: ['hp'], value: filters.hp },
    { name: ['code'], value: filters.code },
  ];

Input 컴포넌트 nesting 상위에 존재하는 Form 컴포넌트의 fields 속성에 위와 같이 부여하였더니, value값이 동적으로 동작하였다.

profile
학습한 지식을 개인적으로 정리하기 위해 만든 블로그입니다 :)

0개의 댓글