jsdoc으로 타입 적용하기
// @ts-check
/**
* @typedef {object} CovidSummary
* @property {Array<object>} County
*/
/**
* @returns {Promise<CovidSummary>}
*/
function fetchCovidSummary() {
const url = "https://api.covid19api.com/summary";
return axios.get(url);
}
any
타입 선언any
타입을 더 적절한 타입으로 변경package.json
파일을 생성.npm i typescript -D
로 타입스크립트 라이브러리를 설치.tsconfig.json
을 생성하고 기본 값을 추가.{
"compilerOptions": {
"allowJs": true,
"target": "ES5",
"outDir": "./dist",
"moduleResolution": "Node",
"lib": ["ES2015", "DOM", "DOM.Iterable"]
},
"include": ["./src/**/*"], // src 하위에 모든 파일
"exclude": ["node_modules", "dist"]
}
tsc
로 타입스크립트 파일을 자바스크립트 파일로 컴파일.// package.json에 build 스크립트 추가
{
"name": "project",
"version": "1.0.0",
"description": "최종 프로젝트 폴더입니다",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc"
},
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^4.8.4"
}
}
js
파일을 모두 ts
파일로 변경.any
선언하기noImplicitAny: true
를 추가.@types
관련 라이브러리를 찾아 설치.any
를 선언.// utils
function $(selector: any) {
return document.querySelector(selector);
}
function getUnixTimestamp(date: any) {
return new Date(date).getTime();
}
// DOM
const confirmedTotal = $(".confirmed-total");
const deathsTotal = $(".deaths");
const recoveredTotal = $(".recovered");
const lastUpdatedTime = $(".last-updated-time");
const rankList = $(".rank-list");
const deathsList = $(".deaths-list");
const recoveredList = $(".recovered-list");
const deathSpinner = createSpinnerElement("deaths-spinner");
const recoveredSpinner = createSpinnerElement("recovered-spinner");
function createSpinnerElement(id: any) {
const wrapperDiv = document.createElement("div");
wrapperDiv.setAttribute("id", id);
wrapperDiv.setAttribute(
"class",
"spinner-wrapper flex justify-center align-center"
);
const spinnerDiv = document.createElement("div");
spinnerDiv.setAttribute("class", "ripple-spinner");
spinnerDiv.appendChild(document.createElement("div"));
spinnerDiv.appendChild(document.createElement("div"));
wrapperDiv.appendChild(spinnerDiv);
return wrapperDiv;
}
// state
let isDeathLoading = false;
let isRecoveredLoading = false;
// api
function fetchCovidSummary() {
const url = "https://api.covid19api.com/summary";
return axios.get(url);
}
function fetchCountryInfo(countryCode: any, status: any) {
// params: confirmed, recovered, deaths
const url = `https://api.covid19api.com/country/${countryCode}/status/${status}`;
return axios.get(url);
}
// methods
function startApp() {
setupData();
initEvents();
}
// events
function initEvents() {
rankList.addEventListener("click", handleListClick);
}
async function handleListClick(event: any) {
let selectedId;
if (
event.target instanceof HTMLParagraphElement ||
event.target instanceof HTMLSpanElement
) {
selectedId = event.target.parentElement.id;
}
if (event.target instanceof HTMLLIElement) {
selectedId = event.target.id;
}
if (isDeathLoading) {
return;
}
clearDeathList();
clearRecoveredList();
startLoadingAnimation();
isDeathLoading = true;
const { data: deathResponse } = await fetchCountryInfo(selectedId, "deaths");
const { data: recoveredResponse } = await fetchCountryInfo(
selectedId,
"recovered"
);
const { data: confirmedResponse } = await fetchCountryInfo(
selectedId,
"confirmed"
);
endLoadingAnimation();
setDeathsList(deathResponse);
setTotalDeathsByCountry(deathResponse);
setRecoveredList(recoveredResponse);
setTotalRecoveredByCountry(recoveredResponse);
setChartData(confirmedResponse);
isDeathLoading = false;
}
function setDeathsList(data: any) {
const sorted = data.sort(
(a: any, b:any) => getUnixTimestamp(b.Date) - getUnixTimestamp(a.Date)
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item-b flex align-center");
const span = document.createElement("span");
span.textContent = value.Cases;
span.setAttribute("class", "deaths");
const p = document.createElement("p");
p.textContent = new Date(value.Date).toLocaleDateString().slice(0, -1);
li.appendChild(span);
li.appendChild(p);
deathsList.appendChild(li);
});
}
function clearDeathList() {
deathsList.innerHTML = null;
}
function setTotalDeathsByCountry(data: any) {
deathsTotal.innerText = data[0].Cases;
}
function setRecoveredList(data: any) {
const sorted = data.sort(
(a: any, b: any) => getUnixTimestamp(b.Date) - getUnixTimestamp(a.Date)
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item-b flex align-center");
const span = document.createElement("span");
span.textContent = value.Cases;
span.setAttribute("class", "recovered");
const p = document.createElement("p");
p.textContent = new Date(value.Date).toLocaleDateString().slice(0, -1);
li.appendChild(span);
li.appendChild(p);
recoveredList.appendChild(li);
});
}
function clearRecoveredList() {
recoveredList.innerHTML = null;
}
function setTotalRecoveredByCountry(data: any) {
recoveredTotal.innerText = data[0].Cases;
}
function startLoadingAnimation() {
deathsList.appendChild(deathSpinner);
recoveredList.appendChild(recoveredSpinner);
}
function endLoadingAnimation() {
deathsList.removeChild(deathSpinner);
recoveredList.removeChild(recoveredSpinner);
}
async function setupData() {
const { data } = await fetchCovidSummary();
setTotalConfirmedNumber(data);
setTotalDeathsByWorld(data);
setTotalRecoveredByWorld(data);
setCountryRanksByConfirmedCases(data);
setLastUpdatedTimestamp(data);
}
function renderChart(data: any, labels: any) {
var ctx = $("#lineChart").getContext("2d");
Chart.defaults.color = "#f5eaea";
Chart.defaults.font.family = "Exo 2";
new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: "Confirmed for the last two weeks",
backgroundColor: "#feb72b",
borderColor: "#feb72b",
data,
},
],
},
options: {},
});
}
function setChartData(data: any) {
const chartData = data.slice(-14).map((value: any) => value.Cases);
const chartLabel = data
.slice(-14)
.map((value: any) => new Date(value.Date).toLocaleDateString().slice(5, -1));
renderChart(chartData, chartLabel);
}
function setTotalConfirmedNumber(data: any) {
confirmedTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalConfirmed),
0
);
}
function setTotalDeathsByWorld(data: any) {
deathsTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalDeaths),
0
);
}
function setTotalRecoveredByWorld(data: any) {
recoveredTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalRecovered),
0
);
}
function setCountryRanksByConfirmedCases(data: any) {
const sorted = data.Countries.sort(
(a: any, b: any) => b.TotalConfirmed - a.TotalConfirmed
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item flex align-center");
li.setAttribute("id", value.Slug);
const span = document.createElement("span");
span.textContent = value.TotalConfirmed;
span.setAttribute("class", "cases");
const p = document.createElement("p");
p.setAttribute("class", "country");
p.textContent = value.Country;
li.appendChild(span);
li.appendChild(p);
rankList.appendChild(li);
});
}
function setLastUpdatedTimestamp(data: any) {
lastUpdatedTime.innerText = new Date(data.Date).toLocaleString();
}
startApp();
strict
모드 설정하기{
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
}
any
로 되어 있는 타입을 최대한 더 적절한 타입으로 변환.as
와 같은 타입단언 키워드를 최대한 사용하지 않도록 고민해서 변경.// utils
function $(selector: string) {
return document.querySelector(selector);
}
function getUnixTimestamp(date: Date) {
return new Date(date).getTime();
}
// DOM
const confirmedTotal = $(".confirmed-total") as HTMLSpanElement;
const deathsTotal = $(".deaths") as HTMLParagraphElement;
const recoveredTotal = $(".recovered") as HTMLParagraphElement;
const lastUpdatedTime = $(".last-updated-time") as HTMLParagraphElement;
const rankList = $(".rank-list");
const deathsList = $(".deaths-list") as HTMLOListElement;
const recoveredList = $(".recovered-list") as HTMLOListElement;
const deathSpinner = createSpinnerElement("deaths-spinner");
const recoveredSpinner = createSpinnerElement("recovered-spinner");
function createSpinnerElement(id: string) {
const wrapperDiv = document.createElement("div");
wrapperDiv.setAttribute("id", id);
wrapperDiv.setAttribute(
"class",
"spinner-wrapper flex justify-center align-center"
);
const spinnerDiv = document.createElement("div");
spinnerDiv.setAttribute("class", "ripple-spinner");
spinnerDiv.appendChild(document.createElement("div"));
spinnerDiv.appendChild(document.createElement("div"));
wrapperDiv.appendChild(spinnerDiv);
return wrapperDiv;
}
// state
let isDeathLoading = false;
let isRecoveredLoading = false;
// api
function fetchCovidSummary() {
const url = "https://api.covid19api.com/summary";
return axios.get(url);
}
enum CovidStatus {
Confirmed = 'confirmed',
Recovered = 'recovered',
Deaths = 'deaths'
}
function fetchCountryInfo(countryCode: string, status: CovidStatus) {
// params: confirmed, recovered, deaths
const url = `https://api.covid19api.com/country/${countryCode}/status/${status}`;
return axios.get(url);
}
// methods
function startApp() {
setupData();
initEvents();
}
// events
function initEvents() {
rankList.addEventListener("click", handleListClick);
}
async function handleListClick(event: any) {
let selectedId;
if (
event.target instanceof HTMLParagraphElement ||
event.target instanceof HTMLSpanElement
) {
selectedId = event.target.parentElement.id;
}
if (event.target instanceof HTMLLIElement) {
selectedId = event.target.id;
}
if (isDeathLoading) {
return;
}
clearDeathList();
clearRecoveredList();
startLoadingAnimation();
isDeathLoading = true;
const { data: deathResponse } = await fetchCountryInfo(
selectedId,
CovidStatus.Deaths
);
const { data: recoveredResponse } = await fetchCountryInfo(
selectedId,
CovidStatus.Recovered
);
const { data: confirmedResponse } = await fetchCountryInfo(
selectedId,
CovidStatus.Confirmed
);
endLoadingAnimation();
setDeathsList(deathResponse);
setTotalDeathsByCountry(deathResponse);
setRecoveredList(recoveredResponse);
setTotalRecoveredByCountry(recoveredResponse);
setChartData(confirmedResponse);
isDeathLoading = false;
}
function setDeathsList(data: any) {
const sorted = data.sort(
(a: any, b:any) => getUnixTimestamp(b.Date) - getUnixTimestamp(a.Date)
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item-b flex align-center");
const span = document.createElement("span");
span.textContent = value.Cases;
span.setAttribute("class", "deaths");
const p = document.createElement("p");
p.textContent = new Date(value.Date).toLocaleDateString().slice(0, -1);
li.appendChild(span);
li.appendChild(p);
deathsList.appendChild(li);
});
}
function clearDeathList() {
deathsList.innerHTML = null;
}
function setTotalDeathsByCountry(data: any) {
deathsTotal.innerText = data[0].Cases;
}
function setRecoveredList(data: any) {
const sorted = data.sort(
(a: any, b: any) => getUnixTimestamp(b.Date) - getUnixTimestamp(a.Date)
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item-b flex align-center");
const span = document.createElement("span");
span.textContent = value.Cases;
span.setAttribute("class", "recovered");
const p = document.createElement("p");
p.textContent = new Date(value.Date).toLocaleDateString().slice(0, -1);
li.appendChild(span);
li.appendChild(p);
recoveredList.appendChild(li);
});
}
function clearRecoveredList() {
recoveredList.innerHTML = null;
}
function setTotalRecoveredByCountry(data: any) {
recoveredTotal.innerText = data[0].Cases;
}
function startLoadingAnimation() {
deathsList.appendChild(deathSpinner);
recoveredList.appendChild(recoveredSpinner);
}
function endLoadingAnimation() {
deathsList.removeChild(deathSpinner);
recoveredList.removeChild(recoveredSpinner);
}
async function setupData() {
const { data } = await fetchCovidSummary();
setTotalConfirmedNumber(data);
setTotalDeathsByWorld(data);
setTotalRecoveredByWorld(data);
setCountryRanksByConfirmedCases(data);
setLastUpdatedTimestamp(data);
}
function renderChart(data: any, labels: any) {
var ctx = $("#lineChart").getContext("2d");
Chart.defaults.color = "#f5eaea";
Chart.defaults.font.family = "Exo 2";
new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: "Confirmed for the last two weeks",
backgroundColor: "#feb72b",
borderColor: "#feb72b",
data,
},
],
},
options: {},
});
}
function setChartData(data: any) {
const chartData = data.slice(-14).map((value: any) => value.Cases);
const chartLabel = data
.slice(-14)
.map((value: any) => new Date(value.Date).toLocaleDateString().slice(5, -1));
renderChart(chartData, chartLabel);
}
function setTotalConfirmedNumber(data: any) {
confirmedTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalConfirmed),
0
);
}
function setTotalDeathsByWorld(data: any) {
deathsTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalDeaths),
0
);
}
function setTotalRecoveredByWorld(data: any) {
recoveredTotal.innerText = data.Countries.reduce(
(total: any, current: any) => (total += current.TotalRecovered),
0
);
}
function setCountryRanksByConfirmedCases(data: any) {
const sorted = data.Countries.sort(
(a: any, b: any) => b.TotalConfirmed - a.TotalConfirmed
);
sorted.forEach((value: any) => {
const li = document.createElement("li");
li.setAttribute("class", "list-item flex align-center");
li.setAttribute("id", value.Slug);
const span = document.createElement("span");
span.textContent = value.TotalConfirmed;
span.setAttribute("class", "cases");
const p = document.createElement("p");
p.setAttribute("class", "country");
p.textContent = value.Country;
li.appendChild(span);
li.appendChild(p);
rankList.appendChild(li);
});
}
function setLastUpdatedTimestamp(data: any) {
lastUpdatedTime.innerText = new Date(data.Date).toLocaleString();
}
startApp();