상태를 가져보자 - 읽은 피드 표시하기

dev__bokyoung·2022년 9월 2일
0

해커뉴스 클론코딩

목록 보기
7/12
post-thumbnail

프롤로그

기능요구사항 ) 사용자가 피드를 읽었다가 돌아왔을 때 읽었는지 안 읽었는지 확인이 필요하다. 이때 상태 관리를 어떻게 할지 생각해 보고 구현해 보자

피드를 읽었는지 안 읽었는지 확인 하는 방법에는 두가지가 있다.

  1. 피드의 마다 고유의 Id 값을 가지고 읽음 표시 데이터를 만들어서 따로 표시 해 놓는 방식
  2. 뉴스피드를 가지고 온 데이터에 새로운 속성 하나를 추가해서 그걸 가지고 확인 하는 방식 -- read

후자의 방식을 선택해 보기로 했다.

1. 새로운 상태 추가

실제로 피드 데이터를 불러 오게 될 때 목록에는 10개씩 나타 나지만 실제로 모두를 불러들이게 된다. 만약 전체 값을 불러와서 비교를 하게 된다면 세상 비효율적일 수가 없다. 그래서 그런 비효율을 없애기 위해 후자의 방식을 택했다.

글을 한번 읽으면 read 데이터를 가지고 있으면 좋다. 목록에서도 피드에서도 참고해야 하니까 전역 상태값을 하나 추가한다. storefeed라는 값을 만들어 배열을 만든다.

const store = {
  currentPage: 1,
  feeds: [],
};

뉴스 피드에도 다이렉트로 URL 을 불러오는게 아니라 store.feeds 데이터로 교체 해 준다.

let newsFeed = store.feeds;

if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(getData(NEWS_URL));
  }

그런데 여기서 makeFeeds 는 무엇일까?
데이터를 가지고 와서 read 했는지 안했는지 알아야 하니까 그 부분의 속성도 추가 시켜 주는 함수이다.

function makeFeeds(feeds) {
  for (let i = 0; i < feeds.length; i++) {
    feeds[i].read = false;
  }

  return feeds;
}

즉, 데이터를 불러와서 makeFeeds 로 가서 read 속성을 추가 해 주고, 그 데이터를 store.feeds 배열에 담아주고 newsfeed 에 다시 담아 질 수 있도록 한다.

2. 상태와 UI 연결

속성을 만들 었으면 글을 읽고 나왔을 때 색상을 변경해 주는 코드를 작성해 보자.

NewsFeed 목록 부분에서 상태값이 read 면 빨간색 bg-red-500 이고, 아니면 그대로 흰색으로 한다.

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>    
    `);
  }

read 값으로 바꾸어 주는 코드는 클릭하고 상세페이지로 전환 될 때 발생 된다. NewsDetail 부분이다. 데이터 전체를 돌려서 id 값이 지금 읽고 있는 id 와 같으면 read 로 전환 시키기

for(let i=0; i < store.feeds.length; i++) {
    if (store.feeds[i].id === Number(id)) {
      store.feeds[i].read = true;
      break;
    }
  }

읽었을 때 상태

3. 전체 코드

const container = document.getElementById('root');
const ajax = 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 = {
  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 newsFeed() {
  let 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);
  
  container.innerHTML = 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('');
  }

  container.innerHTML = 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();

에필로그

기능이 많아지는 것은 규모가 커지면 커질 수록 당연한 일이다. 그러나 코드를 복잡하게 짜서는 안된다. 코드를 복잡하지 않게 프로그래밍 하는 일이 개발자의 역량이라고 할 수 있다.

어떻게 하면 복잡도를 늘리지 않고 규모를 늘릴 수 있을까?가 큰 연구중에 하나라고 한다.

연습이 답이다.

profile
개발하며 얻은 인사이트들을 공유합니다.

0개의 댓글