실패로부터 배우는 미니 프로젝트

Jun_Gyu·2023년 9월 7일
0

개인프로젝트

목록 보기
1/1

❔ 뭘 했길래 질질 짜는가


사실 이번주 화요일부터 내일인 9월 8일 금요일까지

스터디 그룹원들과 함께 1주일짜리 JS를 복습하는것을 목적으로,

DOM을 활용하여 개인 프로젝트를 제작한 후 어떤것을 만들었는지 발표하기로 했다.


혼자서 무언가 뚝스딱스 많이해보지 않은 나였기에,

'일주일이면 대충 하나 만들 수 있겠지??'

라는 어설픈 마인드로 미니 프로젝트를 만들기 시작했다..

결론부터 이야기하자면, 실패로 끝이났다는 것이다 ㅠㅠ


❔ 어떤걸 만들었길래


처음에는 리그오브 레전드의 API를 가져와서 나만의 작은 OP.GG를 만들어 보려고 했으나,

API 키 자체가 서비스를 위한 키를 발급받기 위해서는 초안을 작성해서 제출해야함은 물론,

1주일이라는 대기시간이 필요했음을 간과했다..


첫 계획과는 다르게 데이터를 받아오는 과정에서부터 큰 난관에 부딪혔던 나는 급히 다른 주제를 선정하기로 했으며,

키워드를 통해서 인터넷 뉴스기사를 검색하는 사이트를 부랴부랴 만들기로 했다.

마침 찾아보니, 검색하고자 하는 Topic 키워드를 통해서 인터넷에 올라와 있는 다양한 뉴스기사와 블로그 포스팅을 한번에 검색할 수 있게 해주는 기가막힌 API였다.

하지만 새로운 주제를 찾았다는 기쁨도 잠시, 현재의 나에게는 부끄러운 치명적 단점이 몇가지 존재했으니..

1. TypeScript 사용 미숙
2. React 사용 미숙
3. 컴포넌트 및 라우터 사용경험 부족

그야말로 '이X끼 이거 프론트엔드 개발자 취준생 맞나' 싶은말이 절로 나오는 상황이다.

어쨋든 문제점은 문제점이고, 최대한 구현이 가능하도록 기능 구현을 위주로 이틀 남짓한 시간동안 열심히 삽질 할 준비는 됐다는 것이다.


프로젝트 디렉토리 구조 및 주요 파일


newsFinder/
├── node_modules/
├── src/
│   ├── api/
│   │   └── api.js  // NewsAPI
│   ├── components/
│   │   └── formatDate.js
│   ├── font/
│   ├── index.js
│   └── styles.css
├── .env
├── .gitignore
├── index.html
├── package-lock.json
└── package.json  // parcel, dotenv

디렉토리 구조는 위와 같이 구성했으며 (컴포넌트없는 컴포넌트팀)

'이왕 이렇게 된거 DOM을 위주로 한번 만들어보자' 라는 생각으로 프로젝트를 진행했었다..

api.js

import 'dotenv/config';

export const searchNews = (topic) => {  // form submit 기본 전송기능(action) 막기
  require('dotenv').config();

  const apiKey = process.env.API_KEY;
  const url = `https://newsapi.org/v2/everything?q=${topic}&apiKey=${apiKey}`;
  //검색내용 없을 때는 alert창 & 에러메세지
  if (topic == '') {
    alert('검색할 키워드를 입력해주세요!');
    return Promise.reject(new Error('topic is required'));
  }
  return fetch(url)
    .then((res) => {
      if (!res.ok) {
        alert('해당 키워드로는 검색할 수 없습니다!');
        throw new Error('뉴스 검색에 실패했습니다.');
      }
      return res.json();
    });
}

API_KEY와 같은 경우에는 .env에 환경변수로 지정해둔 값을 불러오도록 했다.

사실, 본 API_KEY와 같은 경우에는 노출된다 하더라도 큰 문제를 불러 일으키는 그런 민감한 정보가 아니라

단순히 뉴스정보를 불러오기 위한 통행증에 불과한 값이긴 하나, 이전에 만들었던 다른 프로젝트에서는 사용하지 못했던 부분이라 직접 적용해보았다.

위의 key값과 input을 통한 검색어 topic를 이용하여 뉴스 데이터를 요청하게 되고, 받아온 데이터를 json타입으로 리턴하도록 코드를 구성했다.


index.js

// newsApi에서 가져온 정보로 DOM에 출력
  searchNews(topic)
    .then((data) => {
      // 데이터 처리 코드
      data.articles.forEach((article) => {
        /* 뉴스 받아오기 */
        const li = document.createElement('li');
        li.className = 'news';
        const a = document.createElement('a');
        a.setAttribute('href', article.url);
        a.setAttribute('target', '_blank');

        /* 썸네일 */
        const thumbnail = document.createElement('div');
        thumbnail.className = 'news-thumbnail';
        const thumbnailImg = document.createElement('img');
        thumbnailImg.setAttribute('src', article.urlToImage);

        /* 뉴스 내용 */
        const contents = document.createElement('div');
        contents.className = 'news-contents';

        /* 작성자 및 날짜 */
        // 작성자
        const author = document.createElement('span');
        author.className = 'news-author';
        author.textContent = article.source.name;

        // 날짜
        const date = document.createElement('span');
        date.className = 'news-date';
        const dateStr = article.publishedAt; // 기사 날짜
        const dateFix = new Date(dateStr);
        date.textContent = formatDate(dateFix);

        /* 뉴슥 내용 - 제목 */
        const title = document.createElement('p');
        title.className = 'news-title';
        title.textContent = article.title;

        /* 뉴슥 내용 - 설명글 */
        const description = document.createElement('p');
        description.className = 'news-description';
        description.textContent = article.description;

        /*만든 DOM요소들 부모에 넣기 */
        newsList.appendChild(li);
        li.appendChild(a);
        // 썸네일 이미지
        a.appendChild(thumbnail);
        thumbnail.append(thumbnailImg);
        // 기사내용
        a.appendChild(contents);

        // 기사 작성자 (뉴스사이트명)
        contents.appendChild(author);
        contents.appendChild(title);
        contents.appendChild(date);
      });

      // 관련 뉴스 없을 때
      let totalResults = data.totalResults;
      if (totalResults == '0') {
        let noResult = document.createElement('strong');
        noResult.className = 'noResult';
        noResult.textContent = '흠.. 기사가 없는 것 같아요..!';
        setTimeout(() => {
          noResult.textContent = '';
        }, 3000);
        newsList.appendChild(noResult);
      }
    })
    .catch((error) => {
      console.log('error : ', error);
    }).finally(() => {
      // 로딩 상태 초기화
    isLoading = false;
    loader.style.display = 'none';
    });

미리 작성해둔 api.js에서 받아 온 json 데이터를 바탕으로 html에 들어갈 요소들을 추가해준다.


기능 시연


검색창에 검색하고자 하는 뉴스의 키워드를 입력하면,

해당 키워드가 담긴 모든 뉴스 기사들의 데이터가 나타나게 된다.

검색 시 카드 형식으로 뉴스 기사의 사이트, 제목, 날짜가 표시되며,

커서를 hover시 scale이 확대되는 효과를 추가하였다.

카드를 클릭하게 되면 해당 기사의 원본 사이트로 이동하는 형식이다.

'맨 위로' 버튼


그리고 프로젝트 처음으로 맨 위로 버튼을 한번 구현해보았다.

처음에는 단순히 scroll의 위치에 따라서 opacity를 0으로 만들어

화면상에서 투명하게 처리를 했었으나,

마우스를 가져다 대면 버튼이 보이지 않는 상태에서도 클릭이 됐었기에 버튼 자체를 감출 방법을 모색했다.

처음에는 display: none으로 버튼을 감추는 방법을 시도했으나,

이미 버튼의 위치를 잡는데 display의 속성을 flex로 잡아뒀던 터라 버튼의 위치가 깨지거나 ease-in-out의 애니메이션 효과가 반영이 되지 않는 등의 문제가 발생했었다.

// 맨 위로 버튼 숨김기능
window.addEventListener('scroll', () => {
  const scrollY = window.scrollY;
  // 스크롤 위치가 특정 값 이상인 경우 버튼 출현
  if (scrollY > 200) {
    $topBtn.style.visibility = 'visible';
    $topBtn.style.opacity = '1';
  } else {
    $topBtn.style.visibility = 'hidden';
    $topBtn.style.opacity = '0';
  }
});

그래서 visibility라는 속성을 통해 버튼의 가시성을 조절하여 애니메이션 효과와 더불어 flex 속성까지 깨지지 않도록 하는 방법을 사용하게 되었다.


반응형 구현


앞선 다른 프로젝트들에서는 다소 미흡했던 부분이었는데,

이번 프로젝트에서는 grid를 활용해서 반응형 페이지를 구현해보았다.

사실 별것없는 페이지로 반응형 구현했다고 하기엔 뭣하지만

그래도 처음으로 제대로 구현시킨 반응형이라 나름 의의가 있었다.


시간은 좀 더 보기 편하게


사실 처음에 JSON 데이터를 받아올때 시간 데이터가 위와 같이 넘어왔었다.

API가 외국계열의 API라 그런지 모르겠지만, 날짜보다는 특히 시간 부분을 볼때 한눈에 확 와닿지를 않아 약간의 수정을 거친 뒤 표시되도록 하였다.

( 특히 시간이 24시간제로 표시됨! )

formatDate.js

export const formatDate = date => {
  const year = date.getFullYear();
  const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 월은 0부터 시작하므로 1을 더하고 2자리로 포맷
  const day = date.getDate().toString().padStart(2, '0'); 
  let hours = date.getHours().toString().padStart(2, '0');
  const minutes = date.getMinutes().toString().padStart(2, '0');

  const ampm = hours < 12 ? '오전' : '오후';
  hours = (hours > 12 ? hours - 12 : hours ).toString().padStart(2, '0');
  const formattedDate = `${year}${month}${day}${ampm} ${hours}${minutes}`;
  
  return formattedDate;
}

먼저 JSON 데이터에서 뉴스 업로드 시간을 받아 온 후

연도, 월, 일, 시, 분 따로따로 변수에 담아주었으며, 리터럴 방식으로 formattedDate 라는 변수에 담아 return 해주었다.

기존에 표시되는 시간은 24시간제였기에 12시를 기준으로 오전, 오후를 구분하여 시간을 새롭게 변경하여 나타내도록 코드를 구성했다.

그리고 각 코드마다 마지막 부분에 padStart()메소드를 사용한 흔적이 있는데,

이는 ES8(ES2017)에 새롭게 등장한 문법으로, 숫자나 문자열의 좌우에 특정한 문자열로 채우는 기능이다.

이를 통해 시간을 표시할때 2자리수가 아니라면 각 앞자리에 0을 추가하여 표시되도록 해주었다.

비교가 쉽도록 두개의 사진을 가져와보았다!

그런데 여기서 문제발생..

여기까지 짧은 시간동안 부랴부랴 기능을 완성하고 배포까지 마쳤는데..

어..? 왜지..?

이때부터 불안감이 엄습해왔다...

정확한 문제를 확인하기 위해서 다시 키워드를 입력하고 네트워크 속성을 보기로 했다.

마지막에 와서 개발자 플랜은 로컬호스트가 아닌 곳에서는 사용을 할 수 없다면서 돈내놓으라고 한다..

뭐 얼마 안하면 내고말지

자기PR의 시간..

2-3일도 안되는 짧은 기간이었지만, 많은 문제점을 발견할 수 있었다.

그 중에서도 가장 중요한것 몇가지만 추려보자면..

1. 계획적이지 않고 즉흥적인 프로젝트의 스타트.

  • 확실히 "~하면 되겠지" 하는 안일한 생각으로 인해 지금의 상황이 만들어지지 않았나 싶다..

  • 추후 프로젝트부터는 반드시 확실한 계획 및 사전확인(또 API 강매때문에 막히면 안되니까)이 필요하다고 절실히 느꼈다..

2. React 및 TypeScript 복습 부족

  • 확실히 이 두가지의 사용에 제한이 있으니, 같은 시간을 놓고 보았을 때 많은 제약 사항이 발생했다.

  • 이번주 주말간 그동안 들었던 온라인 강의를 다시 복습함과 동시에, Youtube를 통해서 특히 React를 활용해서 어떤 방식으로 프로젝트를 구성하는지에 대한 부분을 좀 더 복습해야할 것 같다..

3. ChatGPT 없이는 못살아

  • 사실 프론트엔드를 배우기 시작한 2달 전의 극 초반에 비해서는 조금 줄었지만, 여전히 ChatGPT에 의지하며 코딩을 이어가고 있다..

  • 사용하더라도 내가 ~때문에 사용해야겠다라는 확실한 배경이 없다보니, 내가 만들어낸 프로젝트인지, chatGPT의 산물인지 헷갈릴때가 종종 있다..

  • 시간이 오래 걸리더라도 천천히 내 기술로 만들어갈 필요성을 절실히 느꼈다.


프론트엔드를 배우기 시작한지 2달이 다 되어 가는데, 혼자 무에서 유를 창조하는 과정을 이번에 처음 겪었다.

유의미하게 결과물을 성공적으로 만들어내지는 못했지만, 이번 토이프로젝트의 실패를 말미암아 현재 나의 심각성(?)을 똑바로 직시할 수 있는 좋은 기회였다고 생각한다.

추후에도 다른 많은 프로젝트들을 만들게 될 때는 좀더 성장한 모습을 보여주고자 부단하게 노력해야 함을 크게 느끼게 되었다.

profile
시작은 미약하지만, 그 끝은 창대하리라

0개의 댓글