개발새발 로그

[2023-10-17] TIL - LocalStorage를 이용한 자동저장 에디터를 만들면서.. 본문

TIL

[2023-10-17] TIL - LocalStorage를 이용한 자동저장 에디터를 만들면서..

이즈흐 2023. 10. 17. 23:07

 

미루고 밀려 오늘 드디어 LocalStroage를 이용해서

textarea에 데이터를 입력하면 setTimeout으로 데이터를 일정시간 이후에 localStorage에 저장해

새로고침을 하거나 다른 곳을 이동했다가 와도 불러올 수 있는 기능을 배웠다.

 

근데 이런 간단한 앱을 만들면서도 로직이 내 머리로는 한 번에 이해할 수 없다는 것을 알게 되었다.

그래서 나는 앱을 만들 때마다 그림을 그려가면서 정확하게 해야겠다고 매일매일 생각한다..

 

📝배운 것

1. validation의 습관화
2. 태그의 어트리뷰트로 querySelector("[name=title]");
3. keyup이벤트
4. 제목과 본문을 구분할 때 getAttribute를 이용해 구분하는 방법
5. 디바운스 기법 - 자동저장 기법
6. tempSaveDate를 통해 수정 이력 검사 후 자동저장 데이터 가져오는 로직
7. api 요청에서 res.json() 명령어에 awiat를 빼먹어서든 안된다
8. window에 커스텀이벤트를 등록해서 원하는 데이터를 클릭했을 때 데이터 가져오는 방법

 

 

💡알게 된 것

1. 디바운스 기법

 우리가 노션 같은 거나 티스토리의 글을 작성하다가 새로고침을 하거나 다른 곳에 이동하고 와도 입력했던 글을 불러올 수 있다.

티스토리나 노션이 LocalStorage를 이용해서 자동저장을 하지는 않고, 

찾아보니까 아래처럼 POST요청을 보낸다.(아마 일정 타수를 입력하면 보내는 듯!)

이런 자동저장을 할 때 사용하는 기법이 디바운스 기법이라고 한다.(티스토리는 아닐 듯?)

쉽게 말하면 타이핑을 한 번씩 할 때마다 setTimeout으로 비동기 요청이 계속해서 수행하게 한다.

근데 이 비동기 요청이 수십 번 반복되면 데이터가 무수히 쌓이게 된다.

그래서 해당 요청을변수 안에 넣고, clearTimeout으로 새로운 요청이 생기면 그 즉시 이전 요청을 지워준다.

그렇게 되면 요청이 밀리고 밀려 타이핑이 멈췄을 때 온전한 요청 하나가 진행되는 것이다.

2. fetch API수행 시 주의사항 - json()

우리가 보통 쓰는 fetch API 형식을 보자

export const API_END_POINT = "...";


export const request = async (url, options = {}) => {
  try {
    const res = await fetch(`${API_END_POINT}${url}`, {
      ...options,
      headers: {
        "Content-Type": "application/json",
      },
    });
    if (res.ok) {
      //await를 빼먹는다면 body가 빠진 Promise가 반환됨
      return await res.json();
    }
    throw new Error("API 처리 중 오류 발생");
  } catch (e) {
    console(e.message);
  }
};

데이터를 받아오고 json() 명령어로 자바스크립트에서 쓸 수 있도록 만듭니다.

여기서 json()은 정확히 뭘까?

json()이라는 함수를 호출하면 API를 통해 받아온 데이터가 JSON데이터 타입임을 알리는 것입니다.

웹브라우저는 JSON데이터타입에 맞게 그 데이터를 해석해서 자바스크립트의 데이터타입으로 돌려주게 됩니다.

res.json()은 Promise로 반환한다.

그러면 Promise의 then 명령어를 사용할 수 있다.

 

근데 여기서 await를 꼭 써야 한다고 합니다!

 

왜 꼭 써야 할까요?

먼저 정답을 간단하게 말하면

fetch후에 response데이터는 header가 도착하자마자 프런트엔드에게 주어집니다.

우리가 필요한 body데이터가 아직 도착하지 않았는데 말이죠!!

즉 머리만 오고 몸이 오지 못한 겁니다!

 

그래서 body값을 기다리기 위해서는 await를 넣어줘야 합니다.

 

왜 이런 작동방식일까요??

fetch의 리턴 값은 Promise입니다.

즉 후속처리가 필요합니다.(비동기 처리결과를 전달받아 처리한다.)

그렇기 때문에 then()으로 체이닝 하여 실행하거나 await를 사용해야 하는 것입니다!

 

 

3. 커스텀이벤트

이 기능은 진짜 유용한 것 같았다.

window에 이벤트 등록하고, 원하는 곳에 dispatchEvent를 등록해 이벤트가 일어나면 해당하는 값을 바로 상위 컴포넌트로 가져올 수 있다.

export const initRouter = (onRoute) => {
  //커스텀 이벤트 사용방법
  //window내의 dispatch이벤트가 발행하면
  //Event로 된 데이터를 받아온다.
  window.addEventListener(ROUTE_CHANGE_EVENT_NAME, (e) => {
    //클릭한 post데이터의 id를 받아와서 해당 url로 바꿔주고, route 실행
    const { nextUrl } = e.detail;

    if (nextUrl) {
      history.pushState(null, null, nextUrl);
      onRoute(); // 콜백으로 해줘야 this.route로 url에 따라 이동가능
    }
  });
};

export const push = (nextUrl) => {
  //커스텀 이벤트 사용방법
  //꼭 new CustomEvent로 설정해야 detail에 id값이 들어감
  window.dispatchEvent(
    new CustomEvent(ROUTE_CHANGE_EVENT_NAME, {
      detail: { nextUrl },
    })
  );
};

하지만 이 방법은 에러가 났을 때 어디에서 문제인지 파악하기가 어렵다고 한다.

그래서 많이 사용해서는 안된다고 한다.

 

 

 

🤔회고

자동저장 기능을 가진 앱을 만들면서 사용자의 UX를 위해서 많은 노력이 필요하다는 것을 알게 되었다.

어려웠던 점은 

컴포넌트 끼리의 의존성을 없애기 위해서 콜백함수로 넘기기도하고,

생성자를 통해 하위 컴포넌트들의 setState를 호출해 해당 컴포넌트 state를 바꿔주기도 한다.

근데 이게 너무 꼬이다 보니까 머리로는 이해할 수 없는 상태가 되었다.

 

어디서는 setState에서 API요청을하고,

어디서는 setState에서 또 하위 컴포넌트의 setState를 수행하고..

 

사실 코드도 그리 길지않고 크게보면 진짜 별로 없는 수준인데

이걸 머리로만 생각하려고 하니까 코드에 에러가 생길 때 빠르게 파악하기가 어려웠다.

 

그래서 나는 그림으로 많이 그리게 된다.

나중에 자동저장 앱의 로직을 그림으로 모두 그려서 정리할 것이다..

 

 

728x90
반응형
LIST