전역상태관리 라이브러리 (redux, mobx, recoil, context API...)
등을 사용하지 않는 알림 컴포넌트를 만들고 싶었다.
처음에는 canvas
로서 만들려고 했는데, 곡선 표현이나, 사라지는 속도 및 사용자의 입맛에 맞춰서 스타일을 바꾸기 힘들 것 같아서 div
, span
컴포넌트를 사용해서 만들었다.
불러오는 방법은 antd
의 방법이 이뻐보여, success
, warn
, error
등으로 표현 할 수 있도록 하였고,
css 스타일을 사용자가 지정을 해주면 그대로 입힐 수 있도록 하는 함수 또한 만들었다.
++
주의 해야 할점 style.setProperty
는 Camel
표시법이아니라, -
표시법으로 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();