프론트 개발을 하다 보면 scrollIntoView, container.children, window 같은 DOM API를 너무 자연스럽게 사용하게 된다. 그런데 이 코드를 그대로 React Native로 옮기면?
....
당연히 안 된다...!!!
이유는 간단하다. React Native에는 DOM이란게 존재하지 않기 때문이다.
이 글에서는 DOM API를 왜 React Native에선 쓰면 안되는지, typeof window === 'undefined' 체크는 왜 필요한지, 그리고 cross-platform 환경에서 안전하게 코드를 짜는 방법까지 정리해보겠다.
1. DOM이란?
DOM: Document Object Model. 브라우저가 HTML 문서를 트리 구조로 표현한 객체 모델.
<div id="container">
<p>Hello</p>
<p>World</p>
</div>
예를 들어 이런 html이 있으면 브라우저는 이를 다음과 같은 트리로 변환한다.
- container (Element)
- p (Element)
- p (Element)
그리고 이 트리에 접근하기 위한 API가 바로 DOM API다. 얘넨 모두 브라우저 환경에서만 존재하는 Web API다.
DOM API의 예시:
- element.scrollIntoView()
- container.children
- document.querySelector()
- window.addEventListener()
2. React vs React Native: 렌더링 방식 차이
React (Web)
React는 브라우저 위에서 동작하며 DOM을 조작한다.
- React Component → Virtual DOM
- Virtual DOM → Real DOM
- Real DOM → 브라우저 렌더링
React Native
React Native는 말그대로 native 환경이라 HTML도 DOM도 없다.
- React Component → Native Bridge
- Native Bridge → iOS UIKit / Android View
React Native에는 window, document, Element 자체가 없으니 이런 애들은 못 쓴다.
element.scrollIntoView();
container.children;
window.innerWidth;
3. scrollIntoView는 왜 React Native에서 안 될까?
Web에서의 동작 원리
scrollIntoView()는 특정 DOM element가 보이도록 부모 스크롤 컨테이너를 자동으로 스크롤한다.
내부적으로는:
- 해당 element의 위치 계산
- 스크롤 컨테이너의 scrollTop 변경
- 브라우저가 repaint 한다
모두 DOM 기반이다.
React Native에서는?
React Native에는 DOM 트리가 없고, scrollTop 같은 개념도 없다. React Native에서는 DOM 기반이 아니라 Native 컴포넌트 API 기반으로 스크롤을 제어한다. 대신 ScrollView, FlatList 같은 컴포넌트가 있다.
// ScrollView
scrollViewRef.current.scrollTo({
y: 200,
animated: true,
});
// FlatList
flatListRef.current.scrollToIndex({
index: 10,
animated: true,
});
4. container.children이 안 되는 이유
Web에서는:
const children = container.children;
이는 HTMLCollection을 반환한다.
하지만 React Native에는:
- container도 DOM element가 아님
- children이라는 DOM 프로퍼티도 없음
React Native에서 children은 JSX 레벨의 개념이며, 실제 렌더링된 native view 계층을 직접 순회하지 않는다.
React Native에서 자식 제어는 보통:
- data 기반 렌더링
- FlatList
- map() 렌더링 구조
- ref 기반 접근
으로 해결한다.
5. typeof window === 'undefined' 체크는 왜 필요한가?
window는 어디에?
- 브라우저: 존재함
- Node.js: 없음
- React Native: 없음
- SSR 환경: 없음
따라서 이런 코드를 쓰면 SSR에서 문제가 터진다.
window.addEventListener('scroll', ...)
안전한 체크 방법
if (typeof window !== 'undefined') {
window.addEventListener('scroll', ...)
}
typeof는 선언되지 않은 변수에 접근해도 에러를 발생시키지 않는다.
typeof window // "undefined" (에러 안 남)
window // ReferenceError
6. SSR(Server-Side Rendering)에서의 문제
Next.js 같은 프레임워크에서는 컴포넌트가 서버에서 먼저 실행된다.
이 시점에는 window, document, DOM 모두 없다.
따라서 DOM API를 직접 사용하면 hydration 전에 에러가 난다.
안전한 패턴:
useEffect(() => {
if (typeof window === 'undefined') return;
const handler = () => {
console.log(window.scrollY);
};
window.addEventListener('scroll', handler);
return () => {
window.removeEventListener('scroll', handler);
};
}, []);
useEffect는 클라이언트에서만 실행되기 때문에 SSR에서도 안전하게 쓸 수 있다.
7. Cross-Platform 코드 짜기
React + React Native + SSR을 동시에 고려한다면 다음 전략을 써보자.
1) 환경 분기
const isWeb = typeof window !== 'undefined';
// React Native에서:
import { Platform } from 'react-native';
if (Platform.OS === 'web') {
// web 전용 코드
}
2) abstraction 레이어 만들기
좋은 패턴은 직접 DOM API를 여기저기 쓰지 않는 것이다.
예:
function scrollToBottom(ref) {
if (typeof window !== 'undefined') {
ref.current?.scrollIntoView({ behavior: 'smooth' });
} else {
ref.current?.scrollToEnd?.();
}
}
이렇게 추상화하면 플랫폼 차이를 감출 수 있다.
3) optional chaining 적극 활용
React Native와 Web을 동시에 지원할 때는 아래처럼 defensive programming을 해두는 것이 좋다.
ref.current?.scrollTo?.()
8. 요약
- DOM API는 브라우저 전용이다.
- React Native에는 DOM이 존재하지 않는다.
- scrollIntoView, container.children, window는 RN에서 사용할 수 없다.
- SSR 환경에서도 window는 존재하지 않는다.
- typeof window === 'undefined' 체크는 런타임 에러 방지용이다.
- cross-platform 환경에서는 abstraction + environment check가 필수다.
React는 UI 라이브러리지만, 실행 환경은 여러 개다. Browser, Node (SSR), Native (iOS / Android).
React 코드라고 해서 항상 브라우저 위에서 도는 것은 아니다. 따라서 DOM API를 사용할 때는
- 이 코드는 브라우저 전용인가?
- 서버에서도 실행될 가능성은 없는가?
- React Native에서도 재사용될 가능성은 없는가?
이것들을 따지는걸 습관화하자..~ 런타임 에러를 훨씬 줄일 수 있다.
'Dev & Study' 카테고리의 다른 글
| [git] 브랜치 삭제가 안 될 때: `error: cannot delete branch used by worktree` 해결 방법 (0) | 2026.03.05 |
|---|---|
| 맥 개발환경 세팅, .zshrc 파일 확인하기, export로 환경변수 설정 방법 (1) | 2026.03.04 |
| [javascript] Epoch time이란, 현재 Epoch time 얻는 법 (0) | 2026.03.03 |
| [npm] npm uninstall, 패키지 제거 방법 (0) | 2026.03.03 |
| [Next.js] App Router에서 "use client"란 (0) | 2026.03.03 |