[TIL] 2022-04-18 / 21일차

미음제

·

2022. 4. 18. 22:29

오늘 배운 내용

 

History API

 

하나의 페이지로 주소가 바뀌고 원하는 내용이 바뀌는 것을 SPA라고 한다. React를 이용하면 SPA(Single Page Application)을 만드는 것이 쉽다고 한다(안 해봐서 모르겠다..). 순수 바닐라 자바스크립트로 SPA를 구현하기 위해서 사용하는 것이 History API이다. 해쉬 뱅 대신 브라우저에서 제공하는 API를 이용해 주소를 바꾸는 게 History API이다. 기본적으로 History는 Stack 구조로 이루어져 있다. window 객체를 통해 History에 접근해 사용할 수 있다.

 

 

History API - Web API | MDN

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

developer.mozilla.org

 

window.history.go(); // 원하는 index로 이동
window.history.bakc(); // 뒤로가기
window.history.forward(); // 앞으로가기

 

인터넷을 사용할 때 뒤로 가기, 앞으로 가기 동작은 History에 쌓인 기록들을 따라 동작하게 된다. History에 기록을 쌓기 위해 pushState() 메서드와 replaceState() 메서드를 사용한다.

 

여러 페이지 이동을 하고 콘솔창에서 history를 확인할 수 있다.

 

 

length가 History에 쌓인 기록이라고 볼 수 있다. 따라서 index를 통해 원하는 지점으로의 이동이 가능한 것이다.

 

pushState와 replaceState의 차이점?

 

pushState와 replaceState는 매개변수가 동일하다.

 

history.pushState(null, null, url);
history.replaceState(null, null, url);

 

 

History.pushState() - Web API | MDN

HTML 문서에서, history.pushState() 메서드는 브라우저의 세션 기록 스택에 상태를 추가합니다.

developer.mozilla.org

 

 

History.replaceState() - Web API | MDN

History.replaceState() 메서드는 현재 history를 수정해 메소드의 매개 변수에 전달 된 stateObj, title, URL로 대체합니다. 이 방법은 특히 일부 유저의 동작에 대한 응답으로 history 객체의 상태나 현재 hist

developer.mozilla.org

 

pushSate()는 Stack 구조에 새로운 상태를 추가하는 것이고, replaceState()는 현재 History를 수정해 매개변수로 전달된 인자들을 대체한다. pushState는 새로운 기록을, replaceState는 현재 history 상태를 원하는 것으로 수정한다.

 

// 바닐라 자바스크립트를 통해 노션 클론 코딩
// ap/router.js
const ROUT_CHANGE_EVENT_NAME = 'route-change';
const ROUT_REPLACE_CHANGE_EVENT_NAME = 'route-replace';
export const initRouter = (onRoute) => {
  window.addEventListener(ROUT_CHANGE_EVENT_NAME, (e) => {
    const { nextUrl } = e.detail;
    if (nextUrl) {
      history.pushState(null, null, nextUrl);
      onRoute();
    }
  });

  window.addEventListener(ROUT_REPLACE_CHANGE_EVENT_NAME, (e) => {
    const { nextUrl } = e.detail;
    if (nextUrl) {
      history.replaceState(null, null, nextUrl);
      onRoute();
    }
  });
};

export const push = (nextUrl) => {
  window.dispatchEvent(new CustomEvent(ROUT_CHANGE_EVENT_NAME, {
    detail: {
      nextUrl,
    },
  }));
};

export const restate = (nextUrl) => {
  window.dispatchEvent(new CustomEvent(ROUT_REPLACE_CHANGE_EVENT_NAME, {
    detail: {
      nextUrl,
    },
  }));
};

 

전역 객체에 pushState()와 replaceState()를 걸어두고, 각각의 요청에 맞게 호출하도록 코드를 작성했다. 새로운 url을 추가하는 것과, 기존 history에서 url을 새로 작성할 경우를 나누어 생각해 보았다.

 

  • push : 새로운 post를 작성하고, 해당 post의 페이지로 이동했을 경우
  • replace : 특정 post를 삭제한 후, 현재 주소를 루트로 이동했을 경우

 

이렇게 상황을 분리해 놓고 사용을 했다. 리스트에서 특정 post를 클릭하고 해당 post의 내용을 렌더링 할 때, 해당 post의 id를 url에 추가해 History에 추가해주었다. 

 

특정 post를 삭제한 경우, 더 이상 해당 post의 id를 통해 url로 접근하지 못하게(뒤로 가기나 앞으로 가기를 할 경우 history에 남아 있어 엉뚱한 API 요청을 하게 된다.) 현재 history를 루트 url로 대체해주었다.

 


History API, popstate

 

 

popstate - Web API | MDN

Window 인터페이스의 popstate 이벤트는 사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생합니다. 만약 활성화된 엔트리가 history.pushState() 메서드나 history.replaceState() 메서

developer.mozilla.org

 

새로운 post를 등록하는 버튼 기능과, 특정 post의 id 값에 맞추어 해당 content를 API 호출로 불러오고 렌더링 하는 것까지 성공했다. 그러고 나서 History API를 통해 post의 id에 맞추어 url을 변경해 주었다. 서버를 열고 루트 경로로 들어 간 후, 사이드 바에서 post를 차례로 클릭했을 경우 url 주소도 알맞게 바뀌고 화면에 렌더링까지 성공했다. 그러나 뒤로 가기 버튼, 앞으로 가기 버튼을 실행했을 경우 url만 변경되고 content 내용이 불러와지지 않았다.

 

// src/App.js
this.route = () => {
    const { pathname } = window.location;
    if (pathname === '/') {
      listContainer.render();
      editorContainer.setState({
        postId: null,
      });
    } else if (pathname.indexOf('/documents') === 0) {
      const [, , postId] = pathname.split('/');
      editorContainer.setState({
        postId,
      });
    }
    listContainer.render();
  };
  this.route();
  initRouter(() => this.route());

 

초기 App.js가 실행될 때 route 함수를 호출하고, 현재 url의 pathname에서 루트 경로인지, id 값이 있는지 확인하고 렌더링 하도록 해두었다. 그러나 뒤로 가기 버튼과 앞으로 가기 버튼을 눌렀을 때, route() 함수가 불리지 않고 있었다.

 

강의를 들었을 때, History API를 활용하면서 pushState와 replaceState를 사용한 것 외의 다른 함수는 사용하지 않아서 해당 방법으로 동작하려 하니 문제점을 해결하기가 어려웠다. route() 함수를 호출하도록 여러 시도를 했고, 주말 내내 고민한 끝에 popstate를 알게 되었다.

 

history.pushState() 메서드나 history.replaceState() 메서드에 의해 History 기록이 생성되면 popstate 이벤트의 state 속성은 History 상태의 state 객체의 복사본을 갖고 있게 된다. 앞서 pushState와 replaceState에서 사용하지 않았던 부분이라 넘어갔었는데, 첫 번째 매개변수로 state 속성에 해당 상태를 저장할 수 있다(예를 들면 1번 post의 페이지였을 경우, state에 id 값으로 1을 넣을 수 있다는 식, 하지만 사용하지는 않았다.) 각설하고, popstate는 push나 replace에 의해 생긴 기록에서 해당 history의 상태를 기억해 두고, 뒤로 가기 버튼과 앞으로 가기 버튼이 활성화되어야(history.back() 함수도 동일) 작동한다. 

 

그래서, popstate 함수는 뒤로가기 버튼과 앞으로 가기 버튼이 클릭되었을 때 발생되니 전역 객체에 해당 함수를 호출하는 이벤트를 걸어두면 뒤로 가기 버튼을 눌렀을 때 해당 이벤트가 실행되고, 이벤트 실행 함수에서 렌더링을 호출하면 되지 않을까 생각을 했고, 그렇게 작성을 한 후 해결할 수 있게 되었다.

 

// api/router.js
const ROUT_CHANGE_EVENT_NAME = 'route-change';
const ROUT_REPLACE_CHANGE_EVENT_NAME = 'route-replace';
export const initRouter = (onRoute) => {
  window.addEventListener(ROUT_CHANGE_EVENT_NAME, (e) => {
    const { nextUrl } = e.detail;
    if (nextUrl) {
      history.pushState(null, null, nextUrl);
      onRoute();
    }
  });

  window.addEventListener('popstate', () => {
    onRoute(); // 추가
  });

  window.addEventListener(ROUT_REPLACE_CHANGE_EVENT_NAME, (e) => {
    const { nextUrl } = e.detail;
    if (nextUrl) {
      history.replaceState(null, null, nextUrl);
      onRoute();
    }
  });
};

export const push = (nextUrl) => {
  window.dispatchEvent(new CustomEvent(ROUT_CHANGE_EVENT_NAME, {
    detail: {
      nextUrl,
    },
  }));
};

export const restate = (nextUrl) => {
  window.dispatchEvent(new CustomEvent(ROUT_REPLACE_CHANGE_EVENT_NAME, {
    detail: {
      nextUrl,
    },
  }));
};

 

올바른 설계와 동작인지는 모르겠으나.. 원하는 결과는 얻게 되었다. History에 생긴 기록에 맞추어 뒤로 가기 버튼과 앞으로 가기 버튼을 눌렀을 때, id 값에 맞는 post들을 화면에 렌더링 할 수 있게 되었다.


 

부족했던 점 & 느낀 점

  • history api
  • 컴포넌트 분리

 

코로나 이슈로 수업을 제대로 듣지 못했던 History API 부분에서 굉장히 해맸다. url 주소를 변경해 두었고, 상위 컴포넌트에서 url 주소가 바뀌면 렌더링 하도록 작성했기에 뒤로 가기, 앞으로 가기도 잘 동작할 줄 알았다. 분명 수업에서는 popstate를 사용하지 않았던 것 같은데, 코드를 잘 못 짜서 그런지 url 주소만 바뀌고 원하는 결과를 얻지 못했었다. 금요일부터 일요일까지 엄청 헤매고 헤매었었는데, route 함수가 호출되지 않은 것을 알고 지금의 해결 방법으로 접근할 수 있었다(아직까지 나의 최고의 디버깅 툴은 console.log()...).

 

수업을 제대로 못 들었던 것과 컴포넌트 분리에 익숙하지 않은 문제라고 생각된다. 컴포넌트 간 의존성을 줄이고 독립적으로 동작하기 위한 컴포넌트를 설계해야 하는데, 아직까지 너무 어려운 문제 같다. 지금까지 만들어놓은 개인 프로젝트도 컴포넌트 분리가 엉망인 것 같다. 원하는 곳에서 상태를 적절히 관리하지 못하고 있는 것 같고, 의존성은 당연 높게 관여되어 있는 것 같다. 대략적으로 기본 요구사항은 맞추어 코드를 작성했는데, 엣지 케이스를 발견한다거나 추후에 문제가 생길 것 같다는 의심을 하지 못하고 있다. 20일까지 PR을 해야 하기에 이제 며칠 남지 않았는데 남은 기간 동안 추가 요구사항을 만족하기보다는 할 수 있는 선에서 리팩터링에 집중해야 하지 않을까 싶다.

 

프로젝트를 하는데 온갖 신경이 팔려있고 정신 없다보니 TIL이 밀리게 되었다. 과제를 구현하는데 시간을 다 쏟더라도 꼭 TIL은 사소한 내용이라도 적을 수 있도록 노력해야겠다.

 

참조

 

 

History.pushState() - Web API | MDN

HTML 문서에서, history.pushState() 메서드는 브라우저의 세션 기록 스택에 상태를 추가합니다.

developer.mozilla.org

 

History.pushState() - Web API | MDN

HTML 문서에서, history.pushState() 메서드는 브라우저의 세션 기록 스택에 상태를 추가합니다.

developer.mozilla.org

 

History.replaceState() - Web API | MDN

History.replaceState() 메서드는 현재 history를 수정해 메소드의 매개 변수에 전달 된 stateObj, title, URL로 대체합니다. 이 방법은 특히 일부 유저의 동작에 대한 응답으로 history 객체의 상태나 현재 hist

developer.mozilla.org

 

popstate - Web API | MDN

Window 인터페이스의 popstate 이벤트는 사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생합니다. 만약 활성화된 엔트리가 history.pushState() 메서드나 history.replaceState() 메서

developer.mozilla.org

 

 

https://www.zerocho.com/category/HTML&DOM/post/599d2fb635814200189fe1a7

 

www.zerocho.com

 

 

반응형

'프로그래머스 > 데브코스 프론트엔드' 카테고리의 다른 글

[TIL] 2022-04-21 / 24일차  (0) 2022.04.21
[TIL] 2022-04-19 / 22일차  (0) 2022.04.20
[TIL] 2022-04-12/13 / 17, 18일차  (0) 2022.04.14
[TIL] 2022-04-05 / 12일차  (0) 2022.04.06
[TIL] 2022-04-04 / 11일차  (0) 2022.04.04