TypeScript 5.9는 2026년 1월에 공식 릴리스된 버전으로, 타입 추론 강화와 빌드 성능 개선에 초점을 맞췄어요. 복잡한 유니온 타입 처리나 모듈 해석 방식이 대폭 바뀌면서 프로젝트 설정을 새로 고쳐야 할 시점이 되었네요. 이제 tsc --init 명령어를 실행하면 방대한 주석이 달린 설정 파일 대신 최소한의 권장 옵션만 생성돼요. 기존에 생성된 설정 파일의 주석을 바로 삭제하던 번거로움을 줄여준 거예요. ES 모듈과 현대적인 타겟, 엄격한 타입 체크를 기본으로 하도록 설계된 거죠. 개발 환경이 빠르게 변화하는 만큼 최신 버전에 맞춰 설정을 업데이트하는 게 중요하죠. 특히 신규 프로젝트라면 초기 설정 단계에서부터 권장 옵션을 그대로 적용하는 게 장기 유지보수에 도움이 돼요.

// Recommended Options
{
  "strict": true,
  "jsx": "react-jsx",
  "verbatimModuleSyntax": true,
  "isolatedModules": true,
  "noUncheckedSideEffectImports": true,
  "moduleDetection": "force",
  "skipLibCheck": true
}

실제 프로젝트에 적용할 때는 위 옵션들을 그대로 따르는 게 좋아요. skipLibCheck는 선언 파일의 타입 검사를 건너뛰어 컴파일 속도를 높여주고, moduleDetection은 모든 파일을 모듈로 인식하게 해 전역 스코프 오염을 방지하죠. 이 둘을 함께 설정하면 빌드 효율과 모듈 해석 안정성을 동시에 잡을 수 있어요. verbatimModuleSyntax나 isolatedModules 같은 설정도 함께 적용하면 모던한 개발 환경에 더 잘 적응할 수 있어요. 추가적인 컴파일러 옵션은 에디터 자동 완성이나 공식 tsconfig 레퍼런스를 참고하면 되거든요. 모듈 로딩 방식에도 변화가 생겼어요. import defer 구문을 사용하면 상위 코드가 즉시 실행되지 않도록 지연 로딩이 가능해지네요. 다만 네임스페이스 임포트만 허용되고, 특정 속성이나 기본값을 직접 가져오는 방식은 아직 지원되지 않아요. 이 기능은 현재 프리뷰 단계로 개발자 피드백을 받고 있는 상태예요.

빌드 성능 최적화 차원에서 매퍼에서의 인스턴스화 캐싱이 개선되었어요. 또한 조건부 타입 내로잉 강화도 핵심 변경 사항에 포함되네요. 에디터 환경에서 타입 정보를 볼 때 툴팁 길이가 너무 짧아 답답했던 분들을 위해 js/ts.hover.maximumLength 설정으로 호버 길이를 조절할 수 있고, 기본값도 크게 늘어났네요. 중요한 정보가 생략되곤 했던 불편함도 해소되었죠. 빌드 속도와 타입 안정성을 동시에 잡을 수 있는 만큼, 프로젝트의 tsconfig를 최신 권장 사항에 맞춰 정리해 보는 걸 추천해요. 에디터 자동 완성 기능과 함께 쓰면 설정 탐색 부담이 확 줄어드니까요. 설정 변경 후 반드시 tsc --noEmit로 타입 체크만 먼저 실행해 보세요.

출처

웹 애플리케이션에서 데이터를 요청할 때 사용자가 빠르게 다른 동작을 하거나 페이지를 벗어나면 불필요한 네트워크 호출이 계속 남아 메모리 누수나 상태 충돌을 일으키는 경우가 많습니다. 이런 상황을 막기 위해 fetch API와 함께 AbortController를 결합해 요청을 직접 취소하는 패턴이 많이 쓰이더라고요. 먼저 AbortController 인스턴스를 생성하고, 이를 options 객체의 signal 프로퍼티에 전달하면 됩니다. 사용자가 버튼을 여러 번 클릭하거나 컴포넌트가 마운트 해제될 때 controller.abort() 메서드를 호출하면 해당 fetch 요청은 즉시 중단되고 promise는 abortError를 던집니다. 보통 try-catch 블록에서 이 에러를 잡아두고 다른 로직으로 넘어가면 돼요. 다만 abort 시점과 네트워크 응답이 돌아오는 타이밍을 고려해 중복 렌더링이나 상태 업데이트 조건을 꼼꼼히 확인해야 해요. 코드 예시는 다음과 같이 구성하면 됩니다.

const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .then(res => res.json())
  .then(data => updateState(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('요청이 취소되었습니다.');
    } else {
      throw err;
    }
  });

// 요청 중단 시점
controller.abort();

이렇게 하면 불필요한 백그라운드 작업을 깔끔하게 정리할 수 있고, 특히 실시간 데이터나 검색어 자동 완성 같은 기능에서 반응 속도를 높일 수 있네요. signal 객체를 관리하는 로직만 올바르게 배치하면 네트워크 리소스 낭비를 방지하면서도 애플리케이션의 안정성을 크게 향상시킬 수 있습니다. 요청 취소가 필요한 지점을 명확히 정의하고, 동일한 신호를 공유할 때는 주의해서 사용하면 돼요. 특히 비동기 작업이 여러 개 중첩될 때 signal을 함께 전달하면 모든 하위 호출도 함께 정리할 수 있어요. 코드에서 signal 객체를 변수로 분리해두면 나중에 abort() 호출 위치를 자유롭게 조정할 수 있더라고요.

동적으로 목록이나 버튼이 추가되는 화면 구성을 만들다 보면 매번 새로운 요소에 이벤트 리스너를 붙이는 방식이 부담스러울 때가 많거든요. 저도 처음에는 새로고침이나 데이터 가져오기 후에 반복문을 돌며 일일이 등록하곤 했었네요. 그런데 이렇게 하면 메모리 사용량이 쌓일 뿐만 아니라, 이벤트 바인딩 시점을 놓치면 클릭이 안 되는 경우가 자주 발생하더라고요.

이럴 때 실무에서 가장 많이 쓰는 해결책이 바로 이벤트 위임이에요. 자식 요소 하나하나에 리스너를 달지 않고 공통된 부모 컨테이너에 단 한 번만 등록해두는 방식이죠. 실제로 사용자가 클릭한 대상은 이벤트 타겟으로 얻을 수 있고, 여기에 클로즈스트 메서드를 연결하면 중간에 다른 태그가 끼어 있어도 원하는 기준 요소까지 정확히 거슬러 올라갈 수 있더라고요. 조건부 분기만 잘 추가하면 동적으로 사라지거나 추가되는 요소도 자동으로 감지해줘요.

document.getElementById('item-list').addEventListener('click', (event) => {
  const targetBtn = event.target.closest('.delete-btn');
  if (!targetBtn) return;

  const itemId = targetBtn.dataset.id;
  console.log(`${itemId}번 항목 삭제 요청`);
});

이벤트 위임을 쓸 때 주의해야 할 점이 하나 있어요. 클로즈스트는 순수하게 부모 노드 구조만 탐색할 뿐, 요소가 화면에서 감춰지거나 스타일로 가려진다고 해서 동작이 달라지지 않아요. 만약 부모 컨테이너 자체가 문서에서 제거되면 리스너가 작동하지 않는 건 클로즈스트 때문이 아니라 리스너가 붙은 영역이 사라졌기 때문이죠. 또한 모바일 환경에서 터치와 클릭이 겹쳐서 이벤트가 두 번 발생할 수 있는데, 이때 이벤트 전파 차단을 쓰면 오히려 위임 구조가 끊길 수 있어요. 합성 클릭 문제를 막으려면 터치 시작이나 터치 끝 이벤트에서 이벤트 기본 동작 방지를 쓰는 게 정확한 방법이에요. 클릭 범위를 너무 좁게 잡으면 버튼 내부의 아이콘이나 텍스트를 클릭했을 때 위임이 제대로 작동하지 않을 수 있으니, 실제 동작이 필요한 영역을 명확히 구분하는 게 중요하더라고요.

이렇게 구조를 잡으면 요소가 추가될 때마다 리스너를 다시 붙일 필요가 없네요. 브라우저의 이벤트 버블링 기능을 자연스럽게 활용한 방식이라 성능 측면에서도 확실히 안정적이라고 느껴요. 나중에 컴포넌트 재사용이나 유지보수를 할 때 훨씬 수월해질 테니, 동적 화면을 다루실 때 꼭 한 번쯤 적용해 보시길 권해 드려요.

객체를 복사해서 좀 만졌는데 원본까지 같이 바뀌어버린 경험, 다들 한 번쯤 있으실 거예요. 저도 처음엔 스프레드 연산자로 {...obj} 하면 끝나는 줄 알았거든요. 그런데 이건 얕은 복사라서 한 겹만 떼어낼 뿐이고, 안쪽에 중첩된 객체나 배열은 여전히 원본과 같은 참조를 가리키고 있더라고요. 그래서 안쪽 값을 건드리면 원본도 같이 변합니다.

그래서 예전엔 다들 JSON.parse(JSON.stringify(obj))를 썼죠. 저도 오래 이걸 썼는데, 이게 은근히 조용히 데이터를 망가뜨려요. Date 객체는 ISO 문자열로 바뀌어버리고, Map이나 Set은 그냥 빈 객체가 됩니다. undefined 값이나 함수는 아예 키째로 사라지고요. 순환 참조가 있으면 그 자리에서 TypeError로 터집니다. 사실 에러가 안 나는 쪽이 더 무서워요. Date가 문자열로 바뀐 걸 모르고 넘어갔다가 한참 뒤 엉뚱한 데서 터지거든요.

요즘은 브라우저와 Node에 structuredClone()이 기본으로 들어 있습니다. 라이브러리 설치 없이 전역 함수로 바로 부르면 되고, Date·Map·Set·ArrayBuffer 같은 타입을 그대로 살려주고 순환 참조까지 알아서 처리해 줍니다.

const original = {
  name: '주문',
  createdAt: new Date(),
  items: [{ id: 1, qty: 2 }],
  meta: new Map([['ok', true]])
};

// JSON 방식 - Date는 문자열이 되고 Map은 통째로 사라진다
const bad = JSON.parse(JSON.stringify(original));
console.log(typeof bad.createdAt); // 'string'
console.log(bad.meta);             // {}

// structuredClone - 타입이 그대로 유지된다
const good = structuredClone(original);
console.log(good.createdAt instanceof Date); // true
console.log(good.meta.get('ok'));            // true

// 중첩된 값을 바꿔도 원본은 안전
good.items[0].qty = 99;
console.log(original.items[0].qty);          // 2

 

만능은 아니에요. 함수나 DOM 노드가 섞여 있으면 DataCloneError 예외를 던집니다. 그리고 클래스 인스턴스를 복사하면 프로토타입 체인이 날아가서 그냥 평범한 객체로 돌아와요. instanceof도 false가 되고 클래스 메서드도 사라집니다. getter/setter나 프로퍼티 디스크립터 같은 메타데이터도 복사되지 않고요. 구조화 복제는 '동작'이 아니라 '데이터'만 옮기는 알고리즘이라 그렇더라고요.

지원 범위는 Chrome 98, Firefox 94, Safari 15.4, Node.js 17부터고 2022년 3월에 주요 브라우저를 다 커버했으니, 요즘 환경이면 그냥 써도 무리 없을 거예요. 순수 데이터 객체는 structuredClone으로 처리하고, 클래스 인스턴스나 함수가 섞인 경우엔 직접 복사 로직을 짜거나 lodash cloneDeep 쪽을 쓰는 식으로 나눠 두면 깔끔합니다.

출처: MDN - structuredClone() https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone

아래와 같은 스크립트 코드를 통해 구글맵 api js 라이브러리를 로드하는 경우에 맵이 간헐적으로 표시되지 않는 경우가 있다.

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={Your API KEY}&callback=initMap" async defer></script>

 

원인은 script 태그의 async속성 때문이다.

보통 웹페이지는 낮은 숫자 라인(1)부터 차례대로 읽어들여 로딩한다고 보면 된다.
그 때 위 api 를 로딩하는 태그를 소스상에서 callback 에 설정한 js함수보다 먼저 배치하게 되는경우, 간헐적으로 발생한다.(결국은 로딩문제다)

해결법은 해당 스크립트 태그를 callback함수보다 아래에 배치하거나, async, defer를 빼면 된다.

async는 웹페이지 로딩과 스크립트 파일 로딩을 동시에 실행하기 때문에 웹페이지 로딩보다 api js파일 로딩이 먼저 끝나는 경우에 맵이 표시되지 않는다.

구글의 예제에는 defer가 없는데, 검색한 것들에선 왜 있는지 모르겠다...

1. 프레임 이름

window.frames['frame_name'].location.href = "페이지 주소";

 

2. 프레임 인덱스

window.frames[0].location.href = "페이지 주소";

Math.max( document.body.scrollHeight, document.body.offsetHeight, 

         document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );

+ Recent posts