알림 컴포넌트

Jaewoong2·2020년 11월 21일
1

boilerplate

목록 보기
13/13

알림 컴포넌트

전역상태관리 라이브러리 (redux, mobx, recoil, context API...)
등을 사용하지 않는 알림 컴포넌트를 만들고 싶었다.

처음에는 canvas 로서 만들려고 했는데, 곡선 표현이나, 사라지는 속도 및 사용자의 입맛에 맞춰서 스타일을 바꾸기 힘들 것 같아서 div, span 컴포넌트를 사용해서 만들었다.

불러오는 방법은 antd 의 방법이 이뻐보여, success, warn, error 등으로 표현 할 수 있도록 하였고,

css 스타일을 사용자가 지정을 해주면 그대로 입힐 수 있도록 하는 함수 또한 만들었다.

++
주의 해야 할점 style.setPropertyCamel 표시법이아니라, - 표시법으로 CSS를 설정해줘야 한다는 것이다.

이제는 어디서나 이 함수만 복사해서 사용하면 되니까 개발하기 편할거 같다 - 뿌듯!


import { CSSProperties } from "react";

function sleep(ms = 1000) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}

function settingCssProperty(ref?: HTMLElement, cssProperty?: CSSProperties) {
  if (cssProperty !== null && cssProperty !== undefined) {
    const keys = Object.keys(cssProperty);
    const values = Object.values(cssProperty);
    for (let i = 0; i < keys.length; i++) {
      const index = keys[i]
        .split("")
        .findIndex((value) => value.toLocaleLowerCase() !== value);
      if (index > -1) {
        const upperWord = keys[i][index];
        const key = keys[i].replace(
          upperWord,
          `-${upperWord.toLocaleLowerCase()}`
        );
        ref.style.setProperty(key, values[i]);
      } else {
        ref.style.setProperty(keys[i], values[i]);
      }
    }
  }
}

class Message {
  static count = 0;
  div: HTMLDivElement;
  span: HTMLSpanElement;
  message: string;
  endTime: number;

  
  constructor(message: string) {
    this.div = document.createElement("div");
    this.span = document.createElement("span");
    this.message = message;
    document.body.style.overflow = "hidden";
    this.div.style.position = "absolute";
    this.div.style.zIndex = "10";
    const divStyle = {
      bottom: "0px",
      transform: "translateY(50px)",
      width: "98%",
      display: "flex",
      justifyContent: "center",
      alignItem: "center",
      padding: "5px",
      transition: "transform 1s",
      opacity: "1",
    };

    settingCssProperty(this.div, divStyle);
    this.span.innerText = this.message;
    const spanStyle = {
      backgroundColor: "rgba(255, 255, 255, 0.3)",
      borderRadius: "10px",
      width: "250px",
      height: "38px",
      minWidth: "fit-content",
      display: "flex",
      boxShadow: "2px 2px 3px rgba(0, 0, 0, 0.5)",
      justifyContent: "center",
      alignItems: "center",
    };
    settingCssProperty(this.span, spanStyle);
  }
  success = () => {
    this.span.style.border = "2px solid rgba(0, 195, 0, 0.6)";
    this.span.innerText = this.message + " ✔";
  };
  error = () => {
    this.span.style.border = "2px solid rgba(255, 10, 10, 0.6)";
    this.span.innerText = this.message + " 🚫";
  };
  warn = () => {
    this.span.style.border = "2px solid rgba(255, 255, 50, 0.4)";
    this.span.innerText = this.message + " ❌";
  };

  run = async () => {
    Message.count++;
    if(Message.count > 5) return;
    document.body.appendChild(this.div);
    this.div.appendChild(this.span);
    await sleep(1000);
    this.div.style.transform = "translateY(0px)";
    await sleep(this.endTime);
    this.remove();
  };

  remove = async () => {
    this.div.style.transform = "translateY(50px)";
    await sleep(950);
   	Message.count--; document.body.removeChild(this.div);
  };
}

type MessageType = (
  message: string,
  endTime?: number,
  options?: CSSProperties
) => {
  success: () => void;
  error: () => void;
  warn: () => void;
};

const message: MessageType = (message, endTime = 3000, options) => {
  const msg = new Message(message);
  msg.endTime = endTime;
  settingCssProperty(msg.span, options);
  msg.run();
  return {
    success: () => msg.success(),
    error: () => msg.error(),
    warn: () => msg.warn(),
  };
};

export default message;


import { CSSProperties } from "react";
css Type을 매개변수로 받아서 사용하기 위해서 매개변수의 타입을 설정해주기에서 임포트 하였다. 근데 내가 잘 모르는 건지 구글에 쳐도 css의 type이 따로 없는 거 같아서, React 에서 설정해준 Type을 임포트 하였다. 원래는 따로 사용하는 라이브러리 없이 만들고 싶어했는데 너무 아쉬웠다. 만약 괜찮은 설정을 찾게 되면 사용해야지...

function sleep(ms = 1000) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}

비동기적 사용을 위한 sleep 함수이다. Promise 구문을 사용해서 원하는 만큼의 ms를 받아서 멈추게 해준다. 알림이 생성되고나서 원하는 시간을 받은만큼만 보여지게하고 아래로 사라지게 하였다.

  run = async () => {
    Message.count++;
    if(Message.count > 5) return;
   
    document.body.appendChild(this.div);
    this.div.appendChild(this.span);
    // 스타일 값을 바로 설정해주면, 밑에서 위로 올라가는 느낌으로
    // 설정이 안되기 떄문에,
    // 스타일 값이 설정되기전에 먼저 body에 넣어주고,
    // 그 후, style 값을 설정해주었다.
    await sleep(0);
    this.div.style.transform = "translateY(0px)";
    await sleep(this.endTime);
    this.remove();
  };

잘 모르지만, run() 하기전에 알림 인스턴스를 만들때 부터 body에 div들을 넣어주면 메모리를 많이 사용할꺼 같아서 run() 될 때 body에 append 하도록 하였다.

그리고 static count = 0; 전역변수 count를 설정하여서 보여지는 알림이 5개 이상이 호출이되면 호출이 안되기 하였다.

function settingCssProperty(ref?: HTMLElement, cssProperty?: CSSProperties) {
  if (cssProperty !== null && cssProperty !== undefined) {
    const keys = Object.keys(cssProperty);
    const values = Object.values(cssProperty);
    for (let i = 0; i < keys.length; i++) {
      const index = keys[i]
        .split("")
        .findIndex((value) => value.toLocaleLowerCase() !== value);
      if (index > -1) {
        const upperWord = keys[i][index];
        const key = keys[i].replace(
          upperWord,
          `-${upperWord.toLocaleLowerCase()}`
        );
        ref.style.setProperty(key, values[i]);
      } else {
        ref.style.setProperty(keys[i], values[i]);
      }
    }
  }
}

이 함수는, 일일이 하드코딩 하듯이,

div.style.color = "black";

과 같이 설정해주는 것이 비효율적이라고 생각해서, 먼저 저렇게 설정해주었다가. HtmlElement.style.setProperty(key, value) 메소드를 사용하는 방법을 생각했다. 처음에 설정할때, 그냥 Camel 표시법으로 backgroundColor: rgba(20, 20 20, 0.5) 이런 식으로 값을 넣어줬었는데, 값이 안먹혀서 - 을 넣어야 하는 것을 알았다.

객체 형식으로 받은 cssProperty들을 먼저 key, value를 배열로 값들을 저장해주고,
(Object.keys, Object.Values 메소드를 사용했는데, 그냥 반복문으로 저장해주어도 된다.)

key값들을 반복하며 그 값이 소문자로 모두 바꾼 값이랑 원래 값이랑 같은지를 비교하고, 만약 다른 값이있으면(대문자가 있으면) 그 index 값을 저장한다. 그리고, 그 key 값의 대문자의 위치에 있는 값을 -소문자값 로 바꾸면 된다

그리고 class 를 export 하지 않고, message함수를 따로 만든 이유는, 그냥 어느 컴포넌트에서든 바로 함수를 import해서 사용하는게 더 간편하다고 생각하기 때문이다.

사용

message(message, time, options);
message(message, time, options).success();
message(message, time, options).warn();
message(message, time, options).error();
profile
DFF (Development For Fun)

0개의 댓글