Skip to main content
네이티브
입문네이티브React Native 플랫폼 · 2

JavaScript와 네이티브 실행 경계: 앱의 일은 어디에서 처리되는가

JavaScript 런타임, JavaScript 스레드, UI 스레드와 네이티브 모듈의 역할을 분리하고, 오래 걸리는 작업의 실제 실행 위치를 근거로 응답 지연을 진단한다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

React Native 앱을 조사할 때는 두 축을 따로 본다.

코드의 역할대표 책임
JavaScript 앱 코드상태와 비즈니스 규칙, 사건 처리, 다음 React UI 계산
네이티브 플랫폼 코드실제 호스트 뷰, 운영체제와 기기 기능
실행 위치대표 책임
JavaScript 스레드대부분의 JavaScript 앱 작업과 React 계산
UI 스레드Android·iOS 호스트 뷰의 직접 변경과 플랫폼 UI 작업

두 표는 같은 분류가 아니다. 네이티브 코드가 모두 UI 스레드에서 실행되는 것도 아니며, JavaScript와 플랫폼 기능이 함께 처리하는 작업에는 별도 실행 계약이 있을 수 있다. 오래 걸리는 작업은 파일 확장자가 아니라 실제로 어느 실행 흐름을 얼마나 오래 차지하는지를 확인한다.

이 글을 관통하는 상황: 목록은 스크롤되는데 버튼 응답은 늦다

1편에서 사용한 React Native 0.86 앱의 App.tsx를 다음 코드로 바꾼다. blockFor는 지정한 시간 동안 반복문을 끝내지 않는 진단용 함수다. 운영 코드에서 이런 대기 방식을 사용하면 안 된다.

import { useState } from 'react'
import { Pressable, ScrollView, Text, View } from 'react-native'
 
function blockFor(milliseconds: number) {
  const end = Date.now() + milliseconds
 
  while (Date.now() < end) {
    // JavaScript 실행 흐름을 점유하는 관찰용 반복문
  }
}
 
export default function App() {
  const [status, setStatus] = useState('대기 중')
  const [tapCount, setTapCount] = useState(0)
 
  function handleHeavyWork() {
    blockFor(3000)
    setStatus('계산 완료')
  }
 
  return (
    <View style={{ flex: 1, padding: 24 }}>
      <Pressable onPress={handleHeavyWork}>
        <Text>3초 JavaScript 계산</Text>
      </Pressable>
 
      <Pressable onPress={() => setTapCount(count => count + 1)}>
        <Text>응답 확인: {tapCount}</Text>
      </Pressable>
 
      <Text>상태: {status}</Text>
 
      <ScrollView style={{ flex: 1, marginTop: 16 }}>
        {Array.from({ length: 80 }, (_, index) => (
          <Text key={index}>스크롤 항목 {index + 1}</Text>
        ))}
      </ScrollView>
    </View>
  )
}

다음 순서로 관찰한다.

  1. 목록을 빠르게 위로 밀고 손을 떼 관성 스크롤을 먼저 시작한다.
  2. 목록이 계속 움직이는 동안 3초 JavaScript 계산을 누른다.
  3. 바로 응답 확인도 누르고 숫자와 상태 문구가 언제 바뀌는지 본다.

실행 전에는 관성 스크롤이 이어지는 동안 응답 확인: 0상태: 대기 중이 잠시 남고, 반복문이 끝난 뒤 상태가 계산 완료로 바뀌며 대기하던 버튼 처리도 실행되어 숫자가 증가할 것으로 예상할 수 있다. 손가락 조작 시점에 따라 세부 프레임은 달라져도 구분할 핵심 증거는 다음과 같다.

  • 응답 확인의 JavaScript 처리 함수와 계산 완료 상태 반영은 반복문이 끝날 때까지 기다린다.
  • 네이티브 ScrollView의 스크롤은 JavaScript 스레드가 바쁜 동안에도 이어질 수 있다.

한 화면의 일부가 움직였다는 사실만으로 앱 전체가 응답한다고 결론 내리면 안 된다.

JavaScript 런타임은 브라우저 탭이 아니다

JavaScript 런타임은 “코드가 실행될 수 있는 환경”을 가리킨다. 다음에 나오는 스레드는 “어떤 실행 흐름이 지금 그 작업을 처리하는가”를 가리킨다.

같은 화면에서도 멈춘 경계가 다를 수 있다

따라서 관찰 결과는 다음처럼 읽는다.

목록 스크롤이 이어짐       → 플랫폼 스크롤 작업은 진행될 수 있음
두 번째 onPress가 늦게 반영 → JavaScript 사건 처리는 기다리고 있음
상태 문구가 늦게 바뀜      → 다음 React 계산과 반영도 지연됨

이 예제는 JS 스레드 지연을 구분하기 위한 관찰 도구다. 정확한 성능 수치를 얻으려면 개발용 경고와 검사 비용을 뺀 배포 환경에 가까운 release 빌드에서 다시 측정한다. 어떤 실제 지원 기기까지 측정할지는 29편에서 병목 측정 도구와 함께 정한다.

코드 계층과 스레드는 같은 축이 아니다

그러므로 다음과 같은 등식은 사용하지 않는다.

JavaScript 코드 = 언제나 JavaScript 스레드
네이티브 코드   = 언제나 UI 스레드

대신 문제가 난 함수나 기능마다 호출 위치, 실제 작업 위치와 결과가 돌아오는 위치를 확인한다.

async와 Promise는 실행 위치를 자동으로 바꾸지 않는다

다음 수정은 반복문을 다른 스레드로 옮기지 않는다.

async function handleHeavyWork() {
  await Promise.resolve()
  blockFor(3000)
  setStatus('계산 완료')
}

이미 완료된 Promise를 기다리는 이 코드가 화면의 다음 프레임까지 양보한다고 보장할 수도 없다. 여기서 확인할 수 있는 것은 3초 계산이 다른 스레드로 이동하지 않았다는 사실이다. requestAnimationFrame이나 setTimeout도 그 안의 큰 동기 계산 자체를 다른 스레드로 옮기는 기능은 아니다.

경계를 옮길 때는 실행 계약도 함께 정한다

이 체크섬 계산의 선택지는 작업 성격에 따라 달라진다.

  • 작은 계산이면 JavaScript에서 그대로 실행하고 실제 시간을 측정한다.
  • 나눌 수 있는 계산이면 사용자 입력 사이에 양보하도록 작은 단위로 설계한다.
  • 큰 CPU 작업이면 검증된 별도 실행 환경이나 네이티브 구현을 사용하되 스레드·취소 계약을 확인한다.
  • 저장소·네트워크·센서처럼 플랫폼 API가 비동기 작업을 제공하면 그 API의 완료·오류 계약을 따른다.

구체적인 JSI와 네이티브 모듈 연결 방식은 4편과 6편에서 각각 다룬다. 여기서는 “네이티브로 옮긴다”가 아니라 어느 실행 흐름을 차지하는가를 계약으로 확인한다는 판단만 남긴다.

같은 상황을 다시 진단한다

문제       목록은 움직이지만 버튼과 상태 문구 반영은 3초 늦음
잘못된 판단 화면 일부가 움직이므로 JavaScript도 정상임
관찰       ScrollView는 이어지지만 onPress와 React 상태 반영은 반복문 뒤에 실행됨
원인       긴 동기 계산이 JavaScript 실행 흐름을 점유함
행동       작업 크기를 측정하고 분할하거나 실행 계약이 분명한 별도 경계로 이동
검증       JavaScript 응답과 UI 응답을 나누고 release 빌드에서 다시 확인

풀 리퀘스트에서는 다음을 확인한다.

  • 오래 걸리는 함수가 실제 어느 실행 흐름에서 동작하는지 근거가 있는가?
  • async, Promise나 타이머를 백그라운드 실행 보장으로 오해하지 않았는가?
  • 네이티브 모듈의 실행 위치와 완료·오류·취소 계약을 확인했는가?
  • JavaScript 응답과 UI 스레드 응답을 한 지표로 합치지 않았는가?
  • 성능 결론을 개발 빌드에서 본 한 번의 현상에만 의존하지 않았는가?

기억할 문장은 짧다.

코드가 있는 계층과 코드가 실행되는 스레드는 같은 질문이 아니다.

다음 글에서는 이 여러 경계를 다시 설계한 React Native New Architecture가 어떤 문제를 어떻게 나누는지 살펴본다.

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01JavaScript 동기 계산 중에도 ScrollView가 움직이면 JavaScript 스레드도 여유롭다는 뜻일까?

    정답

    아니다. 플랫폼 UI 스레드가 네이티브 스크롤을 이어 가는 동안 JavaScript 사건 처리와 React 계산은 기다릴 수 있다.

    왜 그런가

    네이티브 UI 동작 한 가지가 이어진다는 사실은 JavaScript 실행 흐름 전체의 응답성을 증명하지 않는다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02오래 걸리는 동기 함수 앞에 async를 붙이고 Promise.resolve를 await하면 계산이 다른 스레드로 이동할까?

    정답

    아니다. await 뒤의 나머지 JavaScript는 나중에 다시 실행될 뿐이며, 같은 동기 계산 자체를 백그라운드 작업으로 바꾸지 않는다.

    왜 그런가

    실행 시점 변경과 실행 위치 변경은 다른 문제다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03네이티브 모듈로 옮긴 계산은 반드시 UI 스레드에서 실행될까?

    정답

    아니다. 네이티브라는 코드 계층과 UI 스레드라는 실행 위치는 다른 축이다. 모듈의 스레드 계약을 확인해야 한다.

    왜 그런가

    UI 스레드는 호스트 뷰를 직접 변경하지만 네이티브 기능은 다른 실행 흐름을 사용할 수 있다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04CPU를 오래 쓰는 체크섬 계산을 네이티브 모듈에 맡길 때 함수 이름과 반환 타입만 정하면 충분할까?

    정답

    부족하다. 어느 실행 흐름에서 계산하는지, 완료·오류·취소를 어떻게 전달하는지와 UI 스레드를 막지 않는다는 계약까지 확인해야 한다.

    왜 그런가

    경계를 옮겨도 실행 계약이 불명확하면 같은 응답 지연이나 새 경쟁 문제가 생길 수 있다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. JavaScript EnvironmentReact Native · 공식 문서 · 확인 2026-08-09
  2. Performance OverviewReact Native · 공식 문서 · 확인 2026-08-09
  3. Threading ModelReact Native · 공식 문서 · 확인 2026-08-09
  4. Render, Commit, and MountReact Native · 공식 문서 · 확인 2026-08-09
  5. Native PlatformReact Native · 공식 문서 · 확인 2026-08-09
  6. Native ModulesReact Native · 공식 문서 · 확인 2026-08-09
  7. async functionMDN Web Docs · 공식 문서 · 확인 2026-08-09
  8. TimersReact Native · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.