ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • notion 클로닝 프로젝트 회고 - History API, SPA
    프로그래머스 데브코스: 빅 데이터 플랫폼 프론트엔드 엔지니어링 2023. 7. 7. 17:01

    노션 클로닝 프로젝트 과제기간이 여차여차 끝났네요! 어려웠지만 많이 배워갈 수 있었던 시간이었던 것 같습니다. 팀원들에게서도 너무너무너무 x 10000 많이 배웠습니다. 아직 기능 구현이 완벽히 되지 않아서 계속 손 볼 것 같아요. 우선 지금까지 프로젝트 만들면서 만났던 문제들이나 알게 됐던 것에 대해 한번 정리하고 가려고 합니다! 🙌

     

    우선 지금까지 만든 결과물은 이렇습니다. 

     

    좌: MainPage ( / )     우: 해당 id에 해당하는 문서 페이지 ( /documents/{id} ) 

     

     

    과제의 요구 사항 중 하나는 'History API를 이용해 SPA 형태로 만드세요.' 였어요. History API와 SPA에 대한 개념이 제대로 잡혀 있지 않은 채로 막무가내로 진행하다 보니까 결국에는 마지막에 페이지와 컴포넌트 틀을 다시 잡게 되는 문제가 발생했어요. 저는 목록만 있는 MainPage와 목록과 해당 컨텐트를 담고 있는 editor를 불러오는 DocumentEditPage 이렇게 2개의 페이지를 만들어야 한다고 생각했는데.... 어우 SPA 풀네임 자체가 SINGLE page application 인데..... (어디서부터 문제였던 걸까.. 여긴가..) 한번 다시 History API와 SPA를 정리해 봅시다!

     

    History API

    MDN에 공식 홈페이지에 따르면 

    DOM의 Window 객체는 history 객체를 통해 브라우저의 세션 기록에 접근할 수 있는 방법을 제공합니다. history는 사용자를 자신의 방문 기록 앞과 뒤로 보내고 기록 스택의 콘텐츠도 조작할 수 있는, 유용한 메서드와 속성을 가집니다.

    라고 나와있습니다. 조금 어렵네요.

     

    history 객체는 브라우저의 세션 기록 = 현재 페이지를 불러온 탭 혹은 프레임이 방문했던 페이지를 조작할 때 사용한다고 생각하면 될 거 같아요. 

     

    코드를 보면서 이해해 봅시다. 

     

    //App.js
    
    import DocumentsPage from './DocumentsPage.js';
    import ListComponent from './ListComponent.js';
    import { initRouter } from './router.js';
    
    export default function App({ target }) {
      const listComponent = new ListComponent({ target });
    
      const docsPage = new DocumentsPage({
        target,
        initialState: {
          id: 'new',
        },
      });
    
      this.route = () => {
        const { pathname } = window.location;
        if (pathname === '/') {
          listComponent.render();
        } else if (pathname.indexOf('/documents/') === 0) {
          const [, , id] = pathname.split('/');
          listComponent.render();
          docsPage.sendId(id);
        }
      };
    
      this.route();
      initRouter(() => this.route());
    }
    //router.js
    
    const ROUTE_CHANGE_EVENT_NAME = 'route-change';
    
    export const initRouter = (onRoute) => {
      window.addEventListener(ROUTE_CHANGE_EVENT_NAME, (e) => {
        const { nextUrl } = e.detail;
    
        if (nextUrl) {
          history.pushState(null, null, nextUrl);
          onRoute();
        }
      });
    };
    
    export const push = (nextUrl) => {
      console.log('확인', nextUrl);
      window.dispatchEvent(
        new CustomEvent(ROUTE_CHANGE_EVENT_NAME, {
          detail: {
            nextUrl,
          },
        })
      );
    };
    
    // DocumnetList.js
    ...
    const { id } = clickedElement.dataset;
    push(`/documents/${id}`);
    ...

     

    구현하고 싶은 기능이 url이 바뀌는것을 잡아내어 해당 url에 해당하는 내용을 페이지 이동 없이(single page) 랜더링 하고 싶은 건데..

     

    최상단인 App에서 initRouter()은 URL 변경 이벤트를 감지하고, 해당 URL에 따른 라우팅 로직을 실행하는 역할을 담당해요. 

     

    차근차근 살펴보면 처음에 MainPage에 접속하게 되면 this.route()를 통해 해당 컴포넌트가 랜더링 됩니다.

    이때까지는 initRouter()가 별다른 역할을 하지 않아요. 하지만 DocumentList 항목을 클릭하게 되면 push()를 통해 id값이 포함된 url이 전달되고 CustomEvent를 통해 nextUrldetail 속성에 포함하여 이벤트 객체를 생성하게 돼요. (저는 이렇게 window 단에서 일어나는 event에 대한 이해가 부족했던 것 같아요. 이벤트는 click 이벤트에만 익숙했지... )

    이렇게 생성된 이벤트는 window.addEventListener()에서 리스닝하는 initRouter() 함수에서 감지됩니다.

     

    nextUrlhistory.pushState()를 호출하여 브라우저의 URL을 변경하는 데 사용됩니다.

    이렇게 history.pushState()를 사용하게 되면 1. 페이지 이 동 없이(single page) url 주소가 바꿀 수 있고 2. 뒤로 가기 버튼이 활성됩니다.

    마지막으로 onRoute()를 실행시켜 해당 url에 해당하는 컴포넌트들을 렌더링 하게 되면 url에 따라 ui 가 바뀌게 됩니다.

     

    SPA

    이렇게 앞서 설명드렸던 history API를 이용해 SPA를 구현해 봤어요. 

    그럼 왜 SPA로 구현을 하면 좋은 걸까요??

     

    먼저 SPA는 "Single Page Application"의 약자로, 단일 페이지 애플리케이션을 의미합니다. SPA는 웹 애플리케이션의 디자인 패턴 중 하나로, 전체 애플리케이션을 단일 페이지로 구성하고, 동적으로 콘텐츠를 업데이트하며 상호작용하는 방식을 말해요. 

     

    일반적인 웹 애플리케이션은 페이지 간의 전환 시마다 서버로부터 새로운 HTML을 받아와서 렌더링 하는 방식입니다. (MPA = multi page application) 그러나 SPA에서는 초기에 필요한 모든 정적 자산(HTML, CSS, JavaScript)을 로드한 후, 이후에는 페이지 간의 전환 시에 필요한 데이터만 서버로부터 비동기적으로 받아와서 동적으로 콘텐츠를 업데이트합니다. 네트워크 비용을 줄일 수 있겠네요!

    즉, 새로운 페이지를 로드하지 않고도 사용자 경험을 제공할 수 있는 것이 SPA의 특징이에요. 

     

    이번 노션 프로젝에서는 vanilla JavaScript만을 이용했지만 SPA는 주로 JavaScript 프레임워크나 라이브러리 (예: React, Angular, Vue.js)와 함께 사용되며, 클라이언트 측에서 렌더링이 이루어져요. 이를 통해 웹 애플리케이션의 성능을 향상하고, 부드러운 사용자 경험을 제공할 수 있다고 해요. 또한 SPA는 웹 애플리케이션의 복잡한 상태 관리와 라우팅을 효율적으로 처리하는 방법을 제공한다고 합니다. 

     

    SPA는 단일 페이지로 구성되지만, 필요에 따라 라우팅을 통해 여러 개의 "가상 페이지"를 사용자에게 제공할 수 있어요. 이렇게 SPA는 사용자가 애플리케이션을 탐색하고 상호작용하는 데 필요한 콘텐츠만 동적으로 로드하여 제공함으로써 웹 애플리케이션의 사용성과 효율성을 향상한다고 합니다. 

     

    하지만 단점도 있겠죠?

     

    SPA는 초기에 필요한 모든 정적 자산(HTML, CSS, JavaScript)을 로드하므로 초기 로딩 속도가 상대적으로 느릴 수 있어요.

     

    그리고 SPA는 동적으로 콘텐츠를 업데이트하기 때문에 초기에 검색 엔진에 적합한 HTML 콘텐츠를 제공하기 어려울 수 있습니다.

    따라서 SPA에서는 추가적인 SEO 대책을 적용해야 할 수도 있습니다.

     

    또한 SPA는 클라이언트 측에서 렌더링 되기 때문에 특히 복잡한 상태 관리와 메모리 누수 관리가 필요할 수 있고 보안에도 취약할 수 있어요.

     

    그리고 마지막은 저도 느꼈던 문제인데 히스토리 관리의 어려움이에요.

    SPA에서는 페이지 전환 시 URL이 변경되지만, 실제로는 페이지 전체가 로드되는 것이 아니기 때문에 브라우저의 히스토리 관리가 어려울 수 있어요. 뒤로 가기, 앞으로 가기, 북마크 등의 기능을 원활하게 지원하기 위해서는 추가적인 라우팅 처리 및 상태 관리가 필요합니다!

     

     


    프로젝트하면서 배운 것도 많고 반성할 것도 많아서 회고글은 이후에 이어서 최소 5개는 더 올라올 것 같아요. ㅋㅋ

    조금 느려도 천천히 채워나가겠습니다!

     

    + 도와주신 팀원 모두 특히 1g2g, MW.park 님 너무 감사해요. 좀 더 신세 지겠습니다. ㅎㅎ

    반응형

    댓글

Designed by Tistory.