반응형
1. 타입 지정하기
변수에 타입을 지정하기 위해서는 해당 변수에 어떤 타입이 담길지를 알아야 한다.
기존 코드에서 변수에 값을 가져오는 메서드에 마우스를 갖다대보자.
가령 document.getElementById()를 보면 해당 아이디가 존재할 경우에는 HTMLElement를 가져올 것이고
그렇지 않으면 null값이 담길 것이다. (너무 당연한 것인데요?)
그래서 container 변수의 타입은 HTMLElement 혹은 null값을 담을 "유니온 타입"으로 지정해준다.
이런 식으로 하단의 변수를 모두 바꿔주면 다음과 같이 바꿀 수 있다.
2. 바꾼 코드
app.ts
type Store = {
currentPage: number;
feeds: NewsFeed[]; // NewsFeed라는 형태가 배열에 들어간다.
};
type NewsFeed = {
id: number;
comments_count: number;
url: string;
user: string;
time_ago: string;
points: number;
title: string;
read?: boolean; // 처음에 네트워크를 가져올 때는 없을 수 있는 값이므로 비어있을 수도 있다.
};
const container: HTMLElement | null = document.getElementById("root");
const ajax: XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL = "https://api.hnpwa.com/v0/news/1.json";
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";
const store: Store = {
currentPage: 1, // 여기에 문자열을 넣으면 바로 오류를 뱉는다.
feeds: [],
};
function getData(url) {
ajax.open("GET", url, false);
ajax.send();
return JSON.parse(ajax.response);
}
function makeFeeds(feeds) {
for (let i = 0; i < feeds.length; i++) {
feeds[i].read = false;
}
return feeds;
}
function updateView(html) {
if (container) {
container.innerHTML = html;
} else {
console.error('최상위 컨테이너가 없어 UI를 진행하지 못합니다.');
}
}
function newsFeed() {
let newsFeed: NewsFeed[] = store.feeds;
const newsList = [];
let template = `
<div class="bg-gray-600 min-h-screen">
<div class="bg-white text-xl">
<div class="mx-auto px-4">
<div class="flex justify-between items-center py-6">
<div class="flex justify-start">
<h1 class="font-extrabold">Hacker News</h1>
</div>
<div class="items-center justify-end">
<a href="#/page/{{__prev_page__}}" class="text-gray-500">
Previous
</a>
<a href="#/page/{{__next_page__}}" class="text-gray-500 ml-4">
Next
</a>
</div>
</div>
</div>
</div>
<div class="p-4 text-2xl text-gray-700">
{{__news_feed__}}
</div>
</div>
`;
if (newsFeed.length === 0) {
newsFeed = store.feeds = makeFeeds(getData(NEWS_URL));
}
for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
newsList.push(`
<div class="p-6 ${newsFeed[i].read ? "bg-red-500" : "bg-white"
} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100">
<div class="flex">
<div class="flex-auto">
<a href="#/show/${newsFeed[i].id}">${newsFeed[i].title}</a>
</div>
<div class="text-center text-sm">
<div class="w-10 text-white bg-green-300 rounded-lg px-0 py-2">${newsFeed[i].comments_count
}</div>
</div>
</div>
<div class="flex mt-3">
<div class="grid grid-cols-3 text-sm text-gray-500">
<div><i class="fas fa-user mr-1"></i>${newsFeed[i].user}</div>
<div><i class="fas fa-heart mr-1"></i>${newsFeed[i].points}</div>
<div><i class="far fa-clock mr-1"></i>${newsFeed[i].time_ago}</div>
</div>
</div>
</div>
`);
}
template = template.replace("{{__news_feed__}}", newsList.join(""));
template = template.replace(
"{{__prev_page__}}",
store.currentPage > 1 ? store.currentPage - 1 : 1
);
template = template.replace("{{__next_page__}}", store.currentPage + 1);
updateView(template);
}
function newsDetail() {
const id = location.hash.substr(7);
const newsContent = getData(CONTENT_URL.replace("@id", id));
let template = `
<div class="bg-gray-600 min-h-screen pb-8">
<div class="bg-white text-xl">
<div class="mx-auto px-4">
<div class="flex justify-between items-center py-6">
<div class="flex justify-start">
<h1 class="font-extrabold">Hacker News</h1>
</div>
<div class="items-center justify-end">
<a href="#/page/${store.currentPage}" class="text-gray-500">
<i class="fa fa-times"></i>
</a>
</div>
</div>
</div>
</div>
<div class="h-full border rounded-xl bg-white m-6 p-4 ">
<h2>${newsContent.title}</h2>
<div class="text-gray-400 h-20">
${newsContent.content}
</div>
{{__comments__}}
</div>
</div>
`;
for (let i = 0; i < store.feeds.length; i++) {
if (store.feeds[i].id === Number(id)) {
store.feeds[i].read = true;
break;
}
}
function makeComment(comments, called = 0) {
const commentString = [];
for (let i = 0; i < comments.length; i++) {
commentString.push(`
<div style="padding-left: ${called * 40}px;" class="mt-4">
<div class="text-gray-400">
<i class="fa fa-sort-up mr-2"></i>
<strong>${comments[i].user}</strong> ${comments[i].time_ago}
</div>
<p class="text-gray-700">${comments[i].content}</p>
</div>
`);
if (comments[i].comments.length > 0) {
commentString.push(makeComment(comments[i].comments, called + 1));
}
}
return commentString.join("");
}
updateView(template.replace('{{__comments__}}', makeComment(newsContent.comments)));
}
function router() {
const routePath = location.hash;
if (routePath === "") {
newsFeed();
} else if (routePath.indexOf("#/page/") >= 0) {
store.currentPage = Number(routePath.substr(7));
newsFeed();
} else {
newsDetail();
}
}
window.addEventListener("hashchange", router);
router();
여기서 눈에 띄는 것은 가장 위에 container 변수에 null이 들어갈 수 있다고 선언하는 순간
container 내부 변수 접근하는 코드들에 빨간색 줄이 그어진다.
null 값이 들어가면 해당 속성에 접근할 수 없기 때문에 그런 것인데,
이럴 때는 null이 아닐 때 처리를 해주어야 한다.
반응형