[Library] 가격 슬라이더 사용(material-ui)

mokyoungg·2020년 7월 24일
1

기업협업 작업을 위해 사용했던 라이브러리이다.
형태와 기능은 input 태그의 range 타입과 같다.
가격필터를 위해 사용했다.

(처음에는 체크박스로 가격을 필터하려고 했으나,
제품마다 가격이 너무 달랐고 무엇보다 기업에서 슬라이더 형태를 원하여 슬라이더 형태를 사용했다)

아무튼 기업협업을 하며 slick과 loading indicator 라이브러리를 사용해서
나름 라이브러리를 이제 이해하고 있었구나라는 나의 생각을 처절하게 짓밟아주었다.


0. 어떤 라이브러리를 쓸 것인가.

이전에 썼던 slick 글에서 openbase를 찬양했다.
그러나 이번엔 딱 마음에 드는 라이브러리를 찾지 못 했다.(내 검색이 틀렸을 수도 있다.)
그래서 주변에 물어보고 귀동냥하여 Material-UI 에 대해 알게 되었다.
리액트 사용자를 위한 UI Framework라고 한다.

Material-UI
React components for faster and easier web development.
Build your own design system, or start with Material Design.
출처: 공식페이지

https://material-ui.com/

1. 설치하기

공식페이지에 아주 설명이 잘 되어있다. 나한테만 안 유명하고 아주 유명한 것처럼 보인다.

https://material-ui.com/getting-started/installation/

npm설치하기

npm install @material-ui/core

Roboto Font 사용하기.
Roboto font를 사용하여 UI를 제작했다고 한다. 그러니 꼭 써야한다.

<html>
<head>
..생략
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
..생략
</head>

이외에도 Font Icons나 SVG Icons 설치에 대한 설명이 있으니 꼭 하자.
아마 하지 않으면 아수라장을 경험할 수도 있다.

2. 작동시키기

개인적으로 라이브러리를 처음 쓸 때는 docs에서 하라는 대로 하는 편이다.
내 것으로 적용하기 전에 일단 화면에 한 번 띄어본다.

material-ui 에서 사용하려는 가격 슬라이더의 형태는 앞서 말했듯이 input 태그의 range 타입이다.
따라서, Components - Inputs - Slider(슬라이더로 되어있다)로 찾아가면 여러가지 모습의
슬라이더 예시와 이를 만들기 위한 코드가 친절히 작성되어 있다.

가격 필터로 사용해야 하기 때문에 가격의 범위가 있어야 한다.
그래서 기준점(?)이 두 개인 슬라이더가 필요하다.
material-ui에선 이를 Range slider라고 부른다. 내가 원하는 모양을 하고 있다.

코드는 다음과 같다.

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Slider from '@material-ui/core/Slider';

const useStyles = makeStyles({
  root: {
    width: 300,
  },
});

function valuetext(value) {
  return `${value}°C`;
}

export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}

앞서 다른 라이브러리를 써서 그런지,
아니면 제공하는 곳에서 이해하기 좋게 코드를 썼는지는 모르겠지만
이제 어느 정도 이해가 된다.

  • 설치한 라이브러리를 import한다.
  • 제공 된 라이브러리의 css 기능을 담당하는 부분이 있다.
  • 제공 된 라이브러리를 위한 함수가 있다.
  • 메인이 되는 <Slider /> 에 함수와 이 부분만을 위한 css 속성이 있다.

화면에 띄어본다. 예시와 같다. 이제 사용하면 된다.

3. 적용하기

처음 생각

  • 최초 코드에는 value의 state 값이 [20, 37] 로 되어있다.
  • 이를 수정하면 아마 처음 보이는 값이 바뀔 것이다.
  • 이후 console.log(value)를 해보니 value 값이 이쁘게 들어온다.
  • 이는 가격 필터에 큰 도움이 될 것이다.

앙증맞다. 아주 깜찍하다. 정말 머리 좋은 사람들은 다르다.
이렇게 사용하기 편하게 만들어 놓다니. 이것이 개발자의 세계인가. 라는 생각으로 신나게 시작했다.

문제 발생

  • 데이터를 fetch하여 보니 상품의 최소값은 70,000원이고 최대값은 320,000원이었다.
    (기업협업의 과제는 의류 상품을 다루는 작업이었다. 현재는 서버 데이터를 사용할 수 없어 mockData를 활용하였다.)
  • 제공하는 라이브러의 최소값은 0이고 최대값은 100이다.
    (최초 예시의 코드는 온도를 위한 슬라이더였다.)
  • 뭐 어떻게든 해서 페이지 최초 render시, 최소값과 최대값을 내가 원하는 숫자로 만들었다.
  • 그러나 움직이지 않았다.
  • 움직일 때도 있는데 그럴 땐 귀신같이 최소값과 최대값이 0과 100으로 바뀌었다.
  • 움직이지 않을 때 자세히 확인하니(눈금을 주고, 슬라이더 크기를 확대해보았다.)
    기준점이 움직이는게 아니고 선이 줄어들고 있었다.
  • 아 이것은 0과 100 사이의 숫자를 위한 슬라이더구나 하고 다른 라이브러리를 찾아헤맸다.

절망적이었다. docs를 계속 보고 수정했다. 영어였다.
(해외의 수많은 라이브러리 docs를 번역해주는 국내 사이트가 있다면 대박이 날 것이다.)

문제 해결

  • docs를 보고 또 보니, min과 max에 대한 설명이 있다.
  • docs를 보고 또 보니, defaultValue라는 개념이 있다.
  • 말이나 글로 표현할 수 없는 어떠한 개념이(보통은 이해라고 부른다) 뇌를 지나간다.
  • 아직도 정확한 이유는 모르겠지만 컴퓨터나 또는 수학의 어떠한 부분,
    또는 제작된 코드 때문에 min,max,defaultValue를 함께 쓰면 내가 가진 문제를 해결할 수 있다.

내가 작성한 코드는 다음과 같다.(리액트와 스타일 컴포넌트를 사용했다.)

import React, { Component, useState, useEffect } from "react";
import styled from "styled-components";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider";
import { TextareaAutosize } from "@material-ui/core";

// 최초 코드가 작성해준 스타일부분(건드려도 됨)
const useStyles = makeStyles({
  root: {
    width: 300,
  },
});

// 최초 코드가 작성해준 함수(사용하지 않음)
function valuetext(value) {
  return `${value}원`;
}

//컴포넌트 선언
const Price = () => {
  const classes = useStyles();
  const [value, setValue] = useState([]); //값
  const [min, setMin] = useState();
  const [max, setMax] = useState();

  useEffect(() => {
    fetch("http://localhost:3000/data/data.json")
      .then((res) => res.json())
      .then((res) => {
        // data 중 가격으로만 이루어진 새로운 배열 생성
        const price = res.data.map((data) => data.price);

        // 가격으로 이루어진 배열에서, 최대값과 최소값 구하기
        const max = price.reduce(function (pre, cur) {
          return pre > cur ? pre : cur;
        });
        const min = price.reduce(function (pre, cur) {
          return pre > cur ? cur : pre;
        });
        setData(res.data);

        // 최소값과 최대값으로 defaultValue 값 설정
        setValue([min, max]);
        setMin(min);
        setMax(max);
      });
  },[]);


  // 슬라이더를 변화시킬 때 마다 value 값 조정
  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  //천단위 , 찍기 위한 함수
  const numberFormat = (num) => {
    if (num > 1000) {
      return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    } else {
      return "0";
    }
  };

  return (
    <Wrap>
      <div className={classes.root}>
        <TextWrap>
          <Text>가격대</Text>
          <PriceInfo>
            {numberFormat(value[0])}원 ~ {numberFormat(value[1])}원
          </PriceInfo>
        </TextWrap>
        <Slider
          value={value} //가격 슬라이더의 값
          defaultValue={[min, max]} //가격 슬라이더 최초 범위
          onChange={handleChange} //슬라이더 변할 때마다 value값을 조정하는 함수
          aria-labelledby="range-slider" //슬라이더 형태
          max={max}
          min={min}
          stpe="10000" //이동 단위(?)
        />
      </div>
    </Wrap>
  );
};

export default Price;

fetch부분

  • componentDidMount가 될 때 fetch 한다.
  • 받아오는 data에서 가격 값만 모은 배열을 생성한다.
  • 배열에서 최소값과 최대값을 구한다.
  • 최초의 value 값으로 최소값과 최대값을 지정한다.
  • min과 max 값을 각각 지정해준다.

<Slider /> 부분

  • defaultValue = {[min, max]} : 슬라이더의 최초 범위를 지정한다.
  • value={value} : 범위로 값, onChange 때 실행되는 함수에 따라 변화한다.
  • max값, min값 : 지정
  • step : 이동 단위..(?) 나의 경우에는 최소값이 7만원, 최대값이 32만원이라 1만원 단위로 이동시키게 했다.

이렇게 하면 움직인다. 슬라이더가 움직이고 그 범위의 시작과 끝(최소값과 최대값)을
console에 찍어보면 기가막히게 들어온다. 이제 이를 사용하여 필터를 쓰면 될 것이다.
(아마 슬라이더가 움직일 수 있는 default 값을 지정해줘야 하는 것 같다.)


번외. 가격 필터 사용하기

확실한 방법은 아니다. 교과서적인 방법은 아니다.

이제 value 값이 이쁘게 찍히니 빠른 속도로 코드를 쓸 수 있을 것이라 생각했다.
실패했다. 그러나 하긴 했다.

코드는 다음과 같다.

import React, { Component, useState, useEffect } from "react";
import styled from "styled-components";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider";
import { TextareaAutosize } from "@material-ui/core";

const useStyles = makeStyles({
  root: {
    width: 300,
  },
});

function valuetext(value) {
  return `${value}원`;
}

const Price = () => {
  const classes = useStyles();
  const [value, setValue] = useState([]);
  const [curVal, setCur] = useState([]);
  const [min, setMin] = useState();
  const [max, setMax] = useState();
  const [data, setData] = useState();
  const [ren, setRen] = useState(true);

  useEffect(() => {
    fetch("http://localhost:3000/data/data.json")
      .then((res) => res.json())
      .then((res) => {
        // data 중 가격으로만 이루어진 새로운 배열 생성
        const price = res.data.map((data) => data.price);

        // 가격으로 이루어진 배열에서, 최대값과 최소값 구하기
        const max = price.reduce(function (pre, cur) {
          return pre > cur ? pre : cur;
        });
        const min = price.reduce(function (pre, cur) {
          return pre > cur ? cur : pre;
        });
        setData(res.data);

        // 최소값과 최대값으로 defaultValue 값 설정
        setValue([min, max]);
        setMin(min);
        setMax(max);
        
        //////가격 필터//////////
        if (curVal.length > 0) {
          const priceData = res.data.filter(
            (data) => curVal[0] <= data.price && data.price <= curVal[1]
          );
          setData(priceData);
          setValue([curVal[0], curVal[1]]);
        }
      });
  }, [ren]);

  // 슬라이더를 변화시킬 때 마다 value / CurValue 값 조정 (CurValue 값을 조정해야한다.)
  const handleChange = (event, newValue) => {
    setValue(newValue);
    setCur(newValue);
  };

  //componentDidUpdate를 위한 state값 변화
  const handleFilter = () => {
    setRen(!ren);
  };

  //천단위 , 찍기 위한 함수
  const numberFormat = (num) => {
    if (num > 1000) {
      return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    } else {
      return "0";
    }
  };

  return (
    <Wrap>
      <div className={classes.root}>
        <TextWrap>
          <Text>가격대</Text>
          <PriceInfo>
            {numberFormat(value[0])}원 ~ {numberFormat(value[1])}원
          </PriceInfo>
        </TextWrap>
        <Slider
          value={value} //가격 슬라이더의 값
          defaultValue={[min, max]} //가격 슬라이더 최초 범위
          onChange={handleChange} //슬라이더 변할 때마다 value값을 조정하는 함수
          aria-labelledby="range-slider" //슬라이더 형태
          max={max}
          min={min}
          stpe="10000" //이동 단위(?)
        />
      </div>
      <div onClick={() => handleFilter()}>제품보기</div>{" "}
      {/* state값 변화시켜 새로 render하기 */}
    </Wrap>
  );
};

export default Price;

해결해야할 것들

  • 필터가 작동할 때 새로 render가 되야한다.
  • render가 새로 될 때마다 value 값이 초기화 된다.(data에서 뽑은 min과 max값으로)
  • 이래선 필터 기능을 사용할 수 없다.

해결 방안

  • value의 값은 계속 바뀌지만 render가 될 떄마다 초기화되기 때문에
    render 직전의 value 값을 기억할 state가 필요하다고 생각했다.
  • 그래서 curValue라는 state를 만들었고 value값과 같은 값으로 변하게 만들었다.
  • render가 되면 value는 변해도 curValue 값은 변하지 않는다.
  • render가 되고 새로 fetch가 될 때 조건문을 걸어 필터를 사용했다.
    (curVal.length > 0 이라는 조건을 건 이유는, 최초 render때는 그 값이 0이기 때문이다.)
  • 들어오는 data를 변한 범위(가격범위)로 필터한다.
  • 슬라이더가 움직일 때마다 render가 되면 큰일이니 원할 때만 render가 되도록
    함수를 만들고 이를 위한 버튼을 만들어준다.
    (ren 이라는 state를 만들고, 이것이 변화할 때마다 componentDidUpdate가 된다)

이렇게 했다.
이게 필터의 정석인가. 아닐 것 같은데.
뭔가 훨씬 더 세련된 코드가 있을 것 같다. 일단은 동작한다. 끝.


글을 쓰면서 계속 라이브러리라고 했는데
material-ui 는 프레임워크다.

이게 뭐랄까.
아는게 많아서 먹고 싶은 것도 많겠네
라는 말이 있는 것처럼 알고 있는게 많아야(뭐든) 시간이 줄어든다.

여기서 시간은 검색시간이다.
라이브러리는 프레임워크든 아직은 다 생소해서 뭘 찾아야 할지 모른다.
그러나 극복한다.

profile
생경하다.

1개의 댓글

comment-user-thumbnail
2021년 1월 2일

ㅠㅠ감사합니다 저 이 글 보고 에어비앤비 가격필터 기능 구현했습니다!!!! 너무너무 친절하게 잘 나와 있어서 큰 도움 받았습니다. 새해 복 많이 받으세요!!!!

답글 달기

관련 채용 정보