뷰 클래스로 코드 구조 개선

 

기존에 함수(function)형태로 작성한 코드들을 ES6의 클래스(class)형태로 바꾼다. function을 class로 바꾸면서 기존의 각 기능 등을 정의하는 함수만으로 정의되어 있는데, 각각의 함수들을 공통적인 부분으로 인스턴스화 할 수 있고, 공통적인 부분 들은 공통적인 클래스로 정의하고 상속을 받아서 사용 할 수 있는 구조를 만들 수 있다.

 

또한 클래스의 특징으로 구조와 기능은 같지만 값만 다른 여러개의 인스턴스를 복제하듯이 만들어 낼 수 있다. (화면의 동일한 컴포넌트들을 값만 다르게 생성할 수 있음)

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
}

interface RouteInfo {
    path: string;
    page: View;
}

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: []
}

class Api{
    url: string;
    ajax: XMLHttpRequest;
    
    constructor(url :string) {
        this.url = url;
        this.ajax = new XMLHttpRequest();
    }

    protected getRequest<AjaxResponse>() :AjaxResponse {
        ...    
    }

}

class NewsFeedApi extends Api{
    getData(): NewsFeed[]{
        ...
    }
}

class NewsDetailApi extends Api{
    getData(): NewsDetail{
        ...
    }
}

abstract class View {
    private template :string;
    private rederTemplate: string;
    private container: HTMLElement;
    private htmlList: string[];

    constructor(containerId: string, template :string){
        const containerElement = document.getElementById(containerId);

        if(!containerElement){
            throw '최상위 컨테이너가 없어 UI를 진행하지 못합니다.'
        }

        this.container = containerElement;
        this.template = template;
        this.rederTemplate = template;
        this.htmlList = [];
    }

    protected updateView() :void {        
        ...
    }

    protected addHtml(htmlString: string): void {
        ...
    }

    protected getHtml(): string{
        ...
    }

    protected setTempateData(key: string, value: string): void{
        this.rederTemplate = this.rederTemplate.replace(`{{__${key}__}}`, value);
    }

    private clearHtmlList(): void{
        this.htmlList = [];
    }

    abstract render(): void;
}

class Router{
    routeTable: RouteInfo[];
    defaultRoute: RouteInfo | null;

    constructor(){        
        window.addEventListener('hashchange', this.route.bind(this));
        
        this.routeTable = [];
        this.defaultRoute = null;
    }
    
    addRoutePath(path: string, page: View): void{
        this.routeTable.push({path, page});
    }
    
    setDefaultPage(page: View): void{
        this.defaultRoute = {path: '', page}
    }
    
    route() {
        ...
    }
}

class NewsFeedView extends View{
    private api: NewsFeedApi;
    private feeds: NewsFeed[];
    private newsTotalPage :number;
    
    constructor(containerId: string){
        ...

        super(containerId, template);

        this.api = new NewsFeedApi(NEWS_URL);
        this.feeds = store.feeds;
        this.newsTotalPage = this.feeds.length / 10;
    
        if(this.feeds.length === 0){
            this.feeds = store.feeds = this.api.getData();
            this.makeFeeds();
        }
        
    }
    
    render(): void {
        store.currentPage = Number(location.hash.substring(7) || 1);
        for(let i=(store.currentPage - 1) * 10; i<store.currentPage * 10; i++){
            const {id, title, comments_count, user, points, time_ago, read} = this.feeds[i];
            this.addHtml(...);
        }
    
        this.setTempateData('news_feed', this.getHtml());
        this.setTempateData('prev_page', `${store.currentPage > 1 ? store.currentPage - 1 : 1}" ${store.currentPage === 1 ? 'style="pointer-events: none;"' : " "}`);
        this.setTempateData('next_page', `${store.currentPage + 1}" ${store.currentPage === this.newsTotalPage ? 'style="pointer-events: none;"' : " "}`);    
    
        this.updateView();
    }

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

class NewsDetailView extends View{
    constructor(containerId: string){        
        ...
        super(containerId, template);
    }
    
    render(): void{
        const id = location.hash.substring(7);
        const api = new NewsDetailApi(CONTENT_URL.replace('@id', id));
        const newsDetail = api.getData();

        for(let i=0; i<store.feeds.length; i++){
            if(store.feeds[i].id === parseInt(id)){
                store.feeds[i].read = true;
                break;
            }
        }
        this.setTempateData('comments', this.makeComment(newsDetail.comments))
        this.setTempateData('currentPage', String(store.currentPage));
        this.setTempateData('title', newsDetail.title);
        this.setTempateData('content', newsDetail.content);
                
        this.updateView();
    }

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

const router: Router = new Router();
const newsFeedView = new NewsFeedView('root');
const newsDetailView = new NewsDetailView('root');

router.setDefaultPage(newsFeedView);

router.addRoutePath('/page/', newsFeedView);
router.addRoutePath('/show/', newsDetailView);

router.route();

 

이번 장에선 지금까지 구현한 코드들의 구조를 개선했다. 함수로 구현했던 부분을 전부 es6의 클래스 형태로 바꿨고 목록이나 내용을 보여주는 클래스는 공통적으로 처리하는 부분을 View 클래스를 통해 상속을 받아 구현 했고 ajax를 통해 데이터를 불러오는 부분은 Api 클래스를 상속 받아 만듦으로써 구조화 시켰으며, 라우팅 처리해주는 부분 역시 클래스화 했다.

 

 

https://bit.ly/37BpXiC

 

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

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

fastcampus.co.kr

 

 

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

 

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

+ Recent posts