[TIL] 3. Skeleton UI로 배운 Promise와 React export 컨벤션

MinseoKim·2026년 8월 3일

1. 오늘의 한 줄 요약

Skeleton UI 실습으로 Promise·async/await 흐름을 다시 정리했고, 리액트 export 컨벤션을 코드 리뷰 관점에서 재정의했다.

2. 배운 내용

★ 핵심 개념

오늘은 Skeleton UI 실습을 진행했다. 통신 시 async/await가 필요하고, axios는 Promise 기반으로 동작한다는 걸 실습으로 확인했다.

스크립트 순서 에러

axios CDN과 내가 작성한 스크립트 순서가 꼬여서 에러가 발생했다. 아래처럼 axios를 먼저, 내 스크립트를 나중에 불러오는 순서를 지켜야 한다.

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="../script/skeleton.js"></script>

Promise와 Promise.all

  • Promise는 결과를 바로 주지 않고, 나중에 성공(resolve)/실패(reject)로 끝날 것을 약속하는 객체다. promise 자체는 데이터가 아니며, 완료 상태로 바뀌어야 값을 꺼낼 수 있다.
  • Promise.all([...])은 배열 안의 여러 Promise를 동시에 시작시키고, 전부 다 끝날 때까지 기다린 뒤 결과를 배열로 묶어 돌려준다. 이때 기준은 더 오래 걸리는 쪽이다.
const [response] = await Promise.all([
    axios.get(`../server/data.json`),
    delay(1500),
]);

→ json 로딩과 1.5초 대기를 동시에 시작해서, 응답이 빨리 와도 최소 1.5초는 스켈레톤 UI가 보이게 만드는 트릭이다.

await과 이벤트 루프

awaitasync 함수 안에서만 쓸 수 있고, Promise가 완료될 때까지 다음 줄로 넘어가지 않는다. 다만 헷갈렸던 부분은 "기다리는 동안 뭐가 넘어오는가"였는데, 정확히는 아래 순서로 동작한다.

async function test() {
    console.log("1. 시작");
    const response = await axios.get('아무 API 주소');
    console.log("2. 데이터 도착:", response.data);
}
test();
console.log("3. 이건 언제 찍힐까?");

await을 만나는 순간 test()는 실행을 멈추고 제어권을 함수 바깥으로 넘긴다. 함수가 끝난 게 아니라, JS 엔진이 "지금 당장 처리 가능한 동기 코드"부터 먼저 실행하는 것이다. 그래서 1 → 3 → 2 순서로 찍힌다. 이 동작 방식을 이벤트 루프라고 부른다는 것도 새로 알게 됐다.


새롭게 알게 된 점 — React

  • Virtual DOM: React/Vue는 state 변경 시 direct access 방식을 쓰기 때문에 속도 이점이 있다. 반면 real DOM은 상태 변경을 스스로 인식하지 못해 매번 리렌더링이 필요하다.
  • Component 기반 설계: props → (component) → element 흐름으로 재사용성을 확보한다. 컴포넌트를 모아 활용하는 것을 composition이라고 부른다.
  • 그동안 페이지와 컴포넌트를 별개 개념으로 혼동하고 있었는데, 리액트 관점에서 페이지도 결국 하나의 컴포넌트라는 걸 이번에 명확히 했다.

    컴포넌트와 페이지를 혼동하긴 했으나, 기존에도 폴더 구조화는 준수하고 있어서 뿌듯!했다.


★ export default vs export — 내 코드 리팩토링 관점에서

기존 DevTI-frontend 코드를 다시 보니, exportexport default를 명확한 기준 없이 혼용하고 있었다. 특히 같은 컴포넌트 계열인 버튼과 인풋 필드에서 이 문제가 드러났다.

  • 버튼은 이미 export default로 통일되어 있었다. 피그마 시안에 약 30종류의 유사한 버튼 명칭이 있는데, 코드에서도 동일한 이름으로 매칭되어야 리뷰 시 혼동이 없기 때문이었다. 버튼은 모든 페이지에서 공통으로 쓰이는 컴포넌트라 이 기준이 특히 중요했다.
  • 인풋 필드는 같은 목적과 기능을 가진 컴포넌트인데도 export(named)로 남아있었다. 버튼과 같은 원칙을 적용해 export default로 옮겨야 한다는 걸 이번 회고로 확인했다.

정리한 기준:

계열방식이유
컴포넌트 (Button, Input 등)export default피그마 네이밍과 일치시켜 리뷰 시 시안-코드 매칭이 쉬움
유틸/서비스 (errorHandler, room services)export (named)함수를 여러 개 내보내는 구조라 이름 강제 일치가 오히려 안전함
페이지export default컴포넌트와 동일한 원칙 적용

페이지는 기존 형식을 유지하되, 팀원 코드에서도 혼재되지 않게 컨벤션으로 확정할 필요는 있어보인다!

이 기준을 팀 컨벤션으로 문서화해두면 새 컴포넌트를 만들 때마다 헷갈리지 않을 것 같다. 다만 급한 리팩토링 대상은 아니라 일단 메모만 해두고 다음 사이클에 반영하기로 했다.


3. 실습 / 적용

  • Skeleton UI + Card UI 렌더링 구조 실습 (showSkeletonUI → 데이터 로드 → renderCards로 교체)
  • Promise.all로 최소 로딩 시간을 보장하는 패턴 적용
  • 기존 프로젝트 export 방식 감사(audit) 및 컨벤션 재정의

4. 문제와 해결

막힌 부분

await이 "기다리는 동안 무슨 일이 일어나는지"를 직관적으로 설명하기 어려웠다. "진행 중 상태가 넘어온다"는 식으로 잘못 이해하고 있었다.

해결 방법 ★

//복습 코드 (단순화)
async function test() {
    console.log("1. 시작");
    const response = await axios.get('아무 API 주소');
    console.log("2. 데이터 도착:", response.data);
}
test();
console.log("3. 이건 언제 찍힐까?");

간단한 테스트 코드(test() 함수 + console.log 순서 확인) 직접 콘솔에서 실행해보며 1 → 3 → 2 순서를 눈으로 확인했다. 이론으로만 읽을 때보다 직접 실행 순서를 관찰하니 이해가 빨랐다.


5. 다음에 할 일

  • 구조 분해 할당(배열/객체) 개념 정리 — 별도 포스팅으로 분리 예정
  • 이벤트 루프 동작 원리 더 깊게 정리 (마이크로태스크 큐 등)
  • export 컨벤션 문서화해서 팀 리드미에 반영

0개의 댓글