상속과 믹스인

 

코드를 작성함에 있어서 중복된 코드가 발생을 하고 그 중복된 코드를 변수나 함수로 만들지만 그것 또한 한계가 있다. 따라서 코드를 목적에 맞게 범주로 나눠서 분류하면서 중복된 코드를 관리 할 수 있다. 그 방법으로 상속을 사용할 것이다.

 

공통 요소로 분류하고 공통 요소에서 확장할 수 있도록 개별 요소를 만드는 식으로 상속을 사용하면 된다.

상속을 사용하는 매커니즘은

1) 클래스 사용

 

중복을 제거하기 위해 클래스를 사용했지만 중복된 코드를 제거하려다 코드가 더 무거워질 수도 있다. 코드 자체가 하는 일이 작은 경우에 그런건데 클래스의 경우는 단순 함수에서 어떠한 구조를 갖는 것이다. 구조를 갖는다는 건 목적을 위한 형식을 갖는 것이고 그럼에서 나중에 클래스가 더 많은 기능을 가지게 될 때 초기의 복잡도는 유지되면서 사용할 때의 단순함은 유지될 수 있다.

 

2) 믹스인 사용

 

기존의 extends의 상속 방법은 상속의 관계를 바꾸고싶다면 코드를 바꿔야한다. 또 class 문법은 다중상속을 지원하지 않는다. 그럴 때 이 믹스인 기법을 사용한다.

 

...

function applyApiMixins(targetClass: any, baseClasses :any[]) :void{
    baseClasses.forEach(baseClass => {
        Object.getOwnPropertyNames(baseClass.prototype).forEach(name => {
            const descriptor = Object.getOwnPropertyDescriptor(baseClass.prototype, name);

            if(descriptor){
                Object.defineProperty(targetClass.prototype, name, descriptor);
            }
        })
    })
}

class Api{
    getRequest<AjaxResponse>(url :string) :AjaxResponse {
        const ajax = new XMLHttpRequest();
        ajax.open('GET', url, false); // method, Url, async
        ajax.send();
    
        return JSON.parse(ajax.response);    
    }
}

class NewsFeedApi{
    getData(): NewsFeed[]{
        return this.getRequest<NewsFeed[]>(NEWS_URL);
    }
}

class NewsDetailApi{
    getData(id :string): NewsDetail{
        return this.getRequest<NewsDetail>(CONTENT_URL.replace('@id', id));
    }
}

interface NewsFeedApi extends Api {};
interface NewsDetailApi extends Api {};

applyApiMixins(NewsFeedApi, [Api]);
applyApiMixins(NewsDetailApi, [Api]);

function newsFeed() :void{
    const api = new NewsFeedApi();
    let newsFeed: NewsFeed[] = store.feeds;
    const newsTotalPage = newsFeed.length / 10;
    const newsList = [];
    ... 
}

function newsDetail() :void{
	...
}

 

코드의 특성상 class의 extends를 사용하는 것만으로도 해결되는 부분이 많기때문에 대부분 믹스인까지 사용하기보다 클래스의 extends를 사용하지만 어떤 유형은 코드베이스의 유연성이 굉장히 많이 필요하기 때문에 extends만으로 부족한 부분은 믹스인을 사용한다.

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

타입과 인터페이스

 

타입스크립트는 type(타입 알리아스)말고도 interface로도 타입을 지정해 줄 수 있다. 타입과 인터페이스는 대체로 비슷하나 근소한 차이가 있다. 

 

1) 타입을 결합하거나 조합하는 방식의 차이

 - 확장되는 형식의 타입에서는 인터페이스를 선호한다.

 - type이 제공하는 유니온(|)타입은 인터페이스가 지원하지 않음 (인터섹션(&)은 가능)

 - 그 외에는 인터페이스를 사용하는 경향이 있다

 

interface Store {
    currentPage :number, 
    feeds :NewsFeed[]
}

interface News {
    readonly id :number,
    readonly url :string,
    readonly user :string,
    readonly time_ago :string,
    readonly title :string,
    readonly content :string
}

interface NewsFeed extends News{    
    readonly comments_count :number,    
    readonly points :number,    
    read? :boolean
}

interface NewsDetail extends News{    
    readonly comments :NewsComments[]
}

interface NewsComments extends News{
    readonly comments: NewsComments[],
    readonly level :number
}

const container: HTMLElement | null = document.querySelector('#root');
const ajax :XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL :string = 'https://api.hnpwa.com/v0/news/1.json'
const CONTENT_URL :string = 'https://api.hnpwa.com/v0/item/@id.json';
const store :Store = {
    ...
}

function getData<AjaxResponse>(url :string) :AjaxResponse {
    ...
}

function makeFeeds(feeds :NewsFeed[]) :NewsFeed[]{
    ...
}

function updateView(html :string) :void{
    ...
}

function newsFeed() :void{
    ...
}

function newsDetail() :void{
    ...
}

function makeComment(comments :NewsComments[]) :string{
    ...
}

function router() :void{
    ...
}

window.addEventListener('hashchange', router);

router();

 

인터페이스의 경우엔 타입 알리아스와 기능적으로는 아주 유사하지만 사용 방식(=의 유무)이나 사용하는 목적의 차이가 있다. 인터섹션(&)을 사용하는 타입 알리아스와 달리 인터페이스는 extends 키워드를 사용하여 타입간의 관계를 확장하는 형식을 나타낸다. 확장되는 형식의 타입에는 인터페이스가 주로 사용된다고 한다. 하지만 인터페이스에는 유니온(|)을 사용할 수 없으므로 유니온(|)을 사용해야 하는 경우는 필수적으로 타입 알리아스를 사용해야 한다.

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

함수의 규격 작성하기

 

타입스크립트는 중복되는 타입의 경우 공통의 타입을 만들어서 사용할 수 있고 변수말고도 함수에도 타입을 지정할 수 있다.

 

type Store = {
    currentPage :number, 
    feeds :NewsFeed[]
}

type News = {
    id :number,    
    url :string,
    user :string,
    time_ago :string,
    title :string,
    content :string
}

type NewsFeed = News & {    
    comments_count :number,    
    points :number,    
    read? :boolean
}

type NewsDetail = News & {    
    comments :NewsComments[]
}

type NewsComments = News & {
    comments: NewsComments[],
    level :number
}

const container: HTMLElement | null = document.querySelector('#root');
const ajax :XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL :string = 'https://api.hnpwa.com/v0/news/1.json'
const CONTENT_URL :string = 'https://api.hnpwa.com/v0/item/@id.json';
const store :Store = {
    currentPage: 1,
    feeds: []
}

function getData<AjaxResponse>(url :string) :AjaxResponse {
    ...
}

function makeFeeds(feeds :NewsFeed[]) :NewsFeed[]{
    ...
}

function updateView(html :string) :void{
    ...
}

function newsFeed() :void{
    let newsFeed: NewsFeed[] = store.feeds;
    const newsTotalPage = newsFeed.length / 10;
    const newsList = [];  

    if(newsFeed.length === 0){
        newsFeed = store.feeds = makeFeeds(getData<NewsFeed[]>(NEWS_URL));
    }
    
    for(let i=(store.currentPage - 1) * 10; i<store.currentPage * 10; i++){
        ...
    }
    ...
}

function newsDetail() :void{
    const id = location.hash.substring(7);
    const newsContent = getData<NewsDetail>(CONTENT_URL.replace('@id', id));
    ...
}

function makeComment(comments :NewsComments[]) :string{
    const commentString = [];
    
    for(let i=0; i<comments.length; i++){
        const comment :NewsComments = comments[i];
        ...
    }
}

function router() :void{
	...
}

window.addEventListener('hashchange', router);

router();

 

GET으로 받아오는 ajax의 타입을 newsFeed type으로 지정했었는데 NewsFeed, NewsDetail, NewsContents 등 API형식을 다르게 써야하는 경우가 있다. 세가지 타입 각각 따로 type을 지정해도 되지만 공통적으로 쓰이는 부분이 있기 때문에 그 부분을 따로 만들어도 된다.

 

News라는 type에 공통적인 값들의 타입을 지정하고 &(인터섹션)를 통해 추가적으로 필요한 부분은 새로운 type (NewsFeed, NewsDetail, NewsContents 등)으로 정의하여 사용하면 된다.

 

 

vsCode의 익스텐션에 'REST Client'라는 것을 추가하면 api 결과를 좀 더 편하게 볼 수 있다.

###
GET https://api.hnpwa.com/v0/news/1.json HTTP/1.1

###
GET https://api.hnpwa.com/v0/item/29816504.json HTTP/1.1

 

postman처럼 사용할 수 있는 것 같다...

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

변수에 타입 지정하기

 

타입스크립트의 타입은 primitive 타입과 object타입으로 나눌 수 있다. primitive 타입은 string, number, boolean 등 원시 타입이고, object타입은 객체 그 자체는 모든 것이 타입이 될 수 있기 때문에 수 없이 많다.

 

type Store = {
    currentPage :number, 
    feeds :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.querySelector('#root');
const ajax :XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL :string = 'https://api.hnpwa.com/v0/news/1.json'
const CONTENT_URL :string = 'https://api.hnpwa.com/v0/item/@id.json';
const store :Store = {
	...
}

function getData(url){
    ...
}

function makeFeeds(feeds){
    ...
}

function updateView(html){
   ...
}

function newsFeed(){
    let newsFeed: NewsFeed[] = store.feeds;
    ...    
}

function newsDetail(){
    ...

    function makeComment(comments, called = 0){
        ...
    }
}

function router(){
    ...
}

window.addEventListener('hashchange', router);

router();

 

먼저 제일 상단에 있는 const에서 선언한 변수들의 타입을 지정했다. dom요소에도 타입을 지정할 수 있는데 dom요소는 대부분 Element 타입과 null 타입이 공존하기 때문에 null 체크를 통한 타입가드를 해주어야한다. updateView() 함수가 id가 root인 dom요소의 타입가드를 해주는 로직의 함수이다. 또 ajax는 XMLHttpRequest 타입을 지정했다.

 

store객체의 currentPage(number), feeds(Array)의 타입을 지정하기 위하여 Store라는 타입을 새로 만들었는데 Store타입 안의 feeds 배열의 타입 또한 지정 해줄 수 있다. feeds 배열의 타입은 getData() 함수를 통해 API의 규격에 맞게 가져온 객체의 형식을 NewsFeed라는 타입으로 지정했다.

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

타입스크립트를 위한 환경설정

 

자바스크립트로 만들어진 해커뉴스 페이지를 타입스크립트로 변환하는 작업을 할 것인데 이런 작업을 포팅이라고 한다.

 

기존에 해커뉴스 페이지는 html파일(index.html)과 js파일(app.js)로 구성되어 있는데 타입스크립트를 사용하기 위해 app.js파일을 app.ts파일로 변환한다. 그리고 tsconfig.json이라는 파일을 생성한다.

 

- tsconfig.json

{
    "compilerOptions": { // 주로 사용하는 부분
        "strict": true, // 타입체크
        "target": "ES5", // 변환할버전
        "module": "commonJS", 
        "alwaysStrict": true,
        "noImplicitAny": true,
        "noImplicitThis": true,
        "sourceMap": true, // html과 js, ts 파일 등을 map 형식으로 나타낸다
        "downlevelIteration": true
    }
}

html은 타입스크립트 파일 자체를 읽어낼 수 없기 때문에 내부적으로 타입스크립트를 자바스크립트로 변환하는 작업이 필요하다. 여기서 config 파일이 그 작업을 도와주고 또 다른 세부 설정도 할 수 있다. parcel 번들러를 실행하고 나면 작업 폴더 내 dist폴더 안에 또 다른 html파일과 js파일이 있는데 형식이 조금 다른 파일이 생성된다. (트랜스파일링이 된 것)

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

상태를 가져보자. 읽은 글 표시하기

 

현재 해커뉴스 페이지에 열람한 글의 상태를 체크해주는 로직이 없기 때문에 해당 로직을 만들것이다.

 

1) 글의 고유 id를 통해 읽음 처리를 하는 데이터 구조를 만든다.

2) 네트워크를 통해 newsFeed를 가져온 데이터에 새로운 속성을 추가한다.

 

const container = document.querySelector('#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){    
	...
}

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

function newsFeed(){
    let newsFeed = store.feeds;
    const newsTotalPage = newsFeed.length / 10;
    const newsList = [];
    let template = `
    ...
    `;

    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}" ${store.currentPage === 1 ? 'style="pointer-events: none;"' : " "}`);
    template = template.replace('{{__next_page__}}', `${store.currentPage + 1}" ${store.currentPage === newsTotalPage ? 'style="pointer-events: none;"' : " "}`);    

    container.innerHTML = template;
}

function newsDetail(){
    const id = location.hash.substring(7);
    const newsContent = getData(CONTENT_URL.replace('@id', id));
    let template = `
        ...
    `;

    for(let i=0; i<store.feeds.length; i++){
        if(store.feeds[i].id === parseInt(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(){
	...
}

window.addEventListener('hashchange', router);

router();

 

먼저 기존 newsFeed는 getData()를 통해 newsFeed를 호출 할 때마다 ajax로 데이터를 불러 들였기 때문에 공공으로 사용하는 store객체에 feeds라는 배열을 추가하여 store의 안에 있는 feeds 배열로 사용한다. ajax로 불러온 데이터를 store에 저장했기 때문에 전역적으로 데이터를 관리할 수 있으며 데이터의 API 형태나 필요한 데이터 구조를 추가할 수 있다. 데이터 읽음 처리를 위해 makeFeed() 함수를 통해 read라는 boolean값 데이터를 추가하여 글을 조회하지 않았을 시엔 false, 조회 하였을 땐 true 값으로 구분한다.

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

tailwindCss를 통한 UI 수정

 

지금까지 만들어진 해커뉴스의 디자인적인 부분은 tailwindCss를 가지고 작업을 할것이다. 그렇게 하기 전에 기존에 있는 해커뉴스의 코드는 template을 가지고 구조적인 부분을 수정했지만 아직도 문제가 있다.

 

template의 <ul>, <li> 태그들은 아직도 반복문을 따로 사용해서 작업하는 부분이나 template의 동적인 데이터를 추가해야 하는 부분은 데이터의 변경이 있을때마다 replace함수를 써 줘야하기 때문에 여러가지 기능들이 추가되면 replace 함수를 그만큼 추가로 사용해야 하는 문제점이 있다. 따라서 템플릿엔진(ejs, pug, nunjucks, handlebars 등)을 사용할 것이다.

 

그 전에 먼저 UI 작업을 먼저 한다.

 

https://fontawesome.com/

 

Font Awesome

The world’s most popular and easiest to use icon set just got an upgrade. More icons. More styles. More Options.

fontawesome.com

폰트와 아이콘을 가볍게 사용할 수 있다. (대부분 아이콘을 사용)

 

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"  integrity="sha512-Fo3rlrZj/k7ujTnHg4CGR2D7kSs0v4LLanw2qksYuRlEzO+tcaEPQogQ0KaoGN26/zrn20ImR1DfuLWnOo7aBA==" crossorigin="anonymous" referrerpolicy="no-referrer" />

마찬가지로 cdn.js 홈페이지에서 cdn으로 가져온다.

 

const container = document.querySelector('#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,
}

function getData(url){    
   ...
}

function newsFeed(){
    const newsFeed = getData(NEWS_URL);
    const newsTotalPage = newsFeed.length / 10;
    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>
    `;
    
    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}" ${store.currentPage === 1 ? 'style="pointer-events: none;"' : " "}`);
    template = template.replace('{{__next_page__}}', `${store.currentPage + 1}" ${store.currentPage === newsTotalPage ? 'style="pointer-events: none;"' : " "}`);    

    container.innerHTML = template;
}

function newsDetail(){
    const id = location.hash.substring(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="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>
    `;

    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(){
    ...
}

window.addEventListener('hashchange', router);

router();

(확연히 기존 template 변수에 사용된 html 태그보다 스타일링을 통해 내용이 길어짐)

 

각 목록 화면과 내용 화면에 UI 부분을 tailwindCss를 사용해서 디자인을 수정했다. html 태그의 클래스를 보게되면 css의 속성(display: flex -> flex, justify-content: space-between -> justify-between 등)을 tailwindCss만의 방식으로 클래스명을 사용하여 스타일링한 것을 알 수 있다.

 

해커뉴스의 내용엔 comment 데이터가 있는데 comment안에 comment가 있는 형식으로 단계가 정해져 있지 않은 구조이다. comment 부분은 makeComment() 함수를 통해 배열 안의 배열을 재귀 호출을 통해서 조회한다. comment배열 안의 comment가 있는지 조건문으로 처리하고 comment안의 배열이 없을 때 해당하는 값을 return 한다.

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

복잡한 UI 구현을 위한 준비 작업 - 템플릿

 

코드의 양과 복잡도는 비례하지 않는다. 코드의 양이 늘어날 수록 복잡도 또한 늘어나면 좋지 않은 구조의 코드라고 생각해야한다. 따라서 템플릿이라는 컨셉을 사용한다.

 

const container = document.querySelector('#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,
}

function getData(url){    
    ...
}

function newsFeed(){
    const newsFeed = getData(NEWS_URL);
    const newsTotalPage = newsFeed.length / 10;
    const newsList = [];
    let template = `
        <div>
            <h1>Hacker News</h1>
            <ul>
                {{__news_feed__}}
            </ul>
            <div>
                <a href="#/page/{{__prev_page__}}">이전 페이지</a>
                <a href="#/page/{{__next_page__}}">다음 페이지</a>
            </div>
        </div>
    `;
    
    for(let i=(store.currentPage - 1) * 10; i<store.currentPage * 10; i++){
        newsList.push(`
            <li>
                <a href="#/show/${newsFeed[i].id}">
                    ${newsFeed[i].title} (${newsFeed[i].comments_count})
                </a>
            </li>
        `);
    }

    template = template.replace('{{__news_feed__}}', newsList.join(''));
    template = template.replace('{{__prev_page__}}', `${store.currentPage > 1 ? store.currentPage - 1 : 1}" ${store.currentPage === 1 ? 'style="pointer-events: none;"' : " "}`);
    template = template.replace('{{__next_page__}}', `${store.currentPage + 1}" ${store.currentPage === newsTotalPage ? 'style="pointer-events: none;"' : " "}`);    

    container.innerHTML = template;
}

function newsDetail(){
    ...
}

function router(){
    ...
}

window.addEventListener('hashchange', router);

router();

 

UI를 변경하기 전에 기존 DOM API를 사용했을 때보다 훨씬 더 보기 직관적이고 개발자도구의 Element와 구조가 비슷한 형태로 어느 부분에 어떤 데이터가 들어갈지 보다 잘 확인할 수 있는 구조가 되었다. 또한 변경이 필요한 부분이라면 어느 부분에서 변경이 일어나는지 한 눈으로 알 수있고 그것이 데이터이던지 화면을 구성하는 코드이던지 변경에 용이하다.

 

 

UI에 tailwind Css프레임워크 사용:

 

https://tailwindcss.com/

 

Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

Documentation for the Tailwind CSS framework.

tailwindcss.com

 

https://cdnjs.com/

 

cdnjs - The #1 free and open source CDN built to make life easier for developers

Simple. Fast. Reliable. Content delivery at its finest. cdnjs is a free and open-source CDN service trusted by over 12.5% of all websites, serving over 200 billion requests each month, powered by Cloudflare. We make it faster and easier to load library fil

cdnjs.com

cdnjs에서 tailwindCss 페이지의 cdn을 검색

 

<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">

cdn 적용

 

tailwind css는 CSS 프레임워크의 일종으로 부트스트랩과 비슷하지만 다소 차이가 있는것 같다. 사용법은 mt-1, ml-6 등 tailwind css 사이트에 있는 컴포넌트나 레이아웃 등을 참고하며 사용할 수 있다. 부트스트랩과 같은 다른 프레임워크들에 비해 기본 스타일 값을 사용하고 필요하다면 디테일하게 커스텀이 가능하다. 부트스트랩의 경우는 완성된 디자인의 컴포넌트를 갖다 쓰는 느낌이지만 tailwind css는 기본 스타일 값을 수정하고 수정하고 나서도 디자인의 일관성을 망치지 않을 수 있다. css-in-js 와는 컨셉이 확연히 다르다. 하지만 단점으로는 html 태그의 class가 하염없이 길어질 수도 있으며 css-in-js의 장점인 어떤 변수에따라 다른 값을 줄 수 있는 스타일링 등은 하기 힘들다.

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

'study' 카테고리의 다른 글

패스트캠퍼스 챌린지 14일차  (0) 2022.02.06
패스트캠퍼스 챌린지 13일차  (0) 2022.02.05
패스트캠퍼스 챌린지 11일차  (0) 2022.02.03
패스트캠퍼스 챌린지 10일차  (0) 2022.02.02
패스트캠퍼스 챌린지 9일차  (0) 2022.02.01

+ Recent posts