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

React Native WebView의 실행 경계: 같은 화면인데 왜 state를 공유하지 않는가

React Native 화면 안의 WebView가 별도 웹 문서와 JavaScript 전역 환경을 가지는 이유를 관찰하고, 초기 입력도 명시적으로 넘겨야 한다는 판단 기준을 익힌다.

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

30초 요약

React Native의 WebView는 컴포넌트 트리 안에 있지만, 그 안에 불러온 HTML은 자신의 Window, 문서와 JavaScript 전역 영역을 사용한다. React Native state 이름과 페이지 전역 변수 이름이 같아도 자동으로 연결되지 않는다.

React Native JavaScript 런타임
  └─ state·컴포넌트 입력 계산
       └─ WebView라는 네이티브 UI를 구성
            └─ 별도 웹 문서의 Window·HTML·JavaScript 상태

반복해서 기억할 문장은 이것이다.

같은 화면 안에 있다는 것은 같은 JavaScript 상태를 공유한다는 뜻이 아니다.

이 글은 WebView의 실행 경계와 첫 page load의 입력까지만 다룬다. 웹에서 앱으로 보내는 메시지는 13편, 앱에서 이미 실행 중인 웹으로 보내는 명령은 14편에서 각각 다룬다.

이 글을 관통하는 상황: 앱의 orderId를 웹이 자동으로 볼 것이라 생각한다

11편의 RnDiffApp은 0.84→0.85 차이만 관찰하는 역사적 upgrade fixture였다. 이 글에서는 그 앱을 암묵적으로 0.86으로 바꾸지 않는다. 저장소 밖의 연습용 상위 디렉터리에서 React Native 0.86.2 앱을 새로 만들고 react-native-webview 14.0.1을 설치한다. 이 글은 iOS와 Android만 검증 범위로 삼는다.

npx @react-native-community/[email protected] init WebBoundaryApp \
  --version 0.86.2 \
  --install-pods true
cd WebBoundaryApp
npm install [email protected]
cd ios
bundle exec pod install
cd ..
npm run android
npm run ios

주문 확인 화면의 React Native state가 order-101이라고 하자. 개발자는 WebView도 같은 JavaScript를 쓰므로 페이지에서 globalThis.orderId를 읽을 수 있다고 가정한다.

originWhitelist는 WebView가 이동을 허용할 웹 출처 패턴 목록이다. ['*']는 모든 출처 패턴을 허용한다. React Native WebView 14.0.1은 인라인 source.html에 이 값을 요구하므로, 외부 link와 navigation이 전혀 없는 이 연습에서만 사용한다. 실제 HTTPS 페이지에서는 알고 있는 출처만 적고 다른 URL로 이동하는 navigation 정책을 따로 검증한다.

다음 App.tsx는 state 공유 가정을 실행 가능한 실패로 만든다.

import React, { useState } from 'react'
import { Button, SafeAreaView, StyleSheet, Text } from 'react-native'
import { WebView } from 'react-native-webview'
 
const BAD_HTML = `<!doctype html>
<html lang="ko">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <body>
    <p id="web-order"></p>
    <p>웹 카운터: <strong id="web-count">0</strong></p>
    <button id="web-plus">웹 카운터 +1</button>
    <script>
      let webCount = 0
      document.querySelector('#web-order').textContent =
        '웹 orderId: ' + String(globalThis.orderId)
      document.querySelector('#web-plus').addEventListener('click', () => {
        webCount += 1
        document.querySelector('#web-count').textContent = String(webCount)
      })
    </script>
  </body>
</html>`
 
export default function App() {
  const [orderId, setOrderId] = useState('order-101')
 
  return (
    <SafeAreaView style={styles.screen}>
      <Text>RN orderId: {orderId}</Text>
      <Button title="RN 주문을 202로 변경" onPress={() => setOrderId('order-202')} />
      <WebView
        originWhitelist={['*']} // navigation 없는 인라인 fixture 전용
        source={{ html: BAD_HTML }}
        style={styles.webview}
      />
    </SafeAreaView>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12 },
  webview: { flex: 1, borderWidth: 1 },
})

실행 전에 세 결과를 예측한다.

  1. 첫 화면의 RN orderId와 웹 orderId가 둘 다 order-101일까?
  2. 웹 카운터를 1로 만든 일이 React Native state를 바꿀까?
  3. RN 주문을 order-202로 바꾸면 페이지의 globalThis.orderId도 자동으로 바뀔까?

Android emulator와 iOS Simulator에서 같은 순서로 실행한다.

첫 화면
RN orderId: order-101
웹 orderId: undefined
웹 카운터: 0
 
웹 카운터 +1 뒤
RN orderId: order-101
웹 orderId: undefined
웹 카운터: 1
 
RN 주문을 202로 변경한 뒤
RN orderId: order-202
웹 orderId: undefined
웹 카운터: 1

웹 버튼은 웹 문서의 webCount만 바꾼다. React Native 버튼은 React state만 바꾼다. 같은 화면에 보이지만 어느 쪽도 상대 전역 변수에 자동으로 쓰지 않는다.

같은 화면 안이라는 말은 같은 JavaScript 영역이라는 뜻이 아니다

React Native 쪽 JavaScript 런타임은 React 컴포넌트의 state와 props를 계산한다. WebView는 그 계산 결과로 만들어지는 네이티브 UI 요소이지만, 그 안의 페이지는 새 문서와 웹 전역 환경을 가진다.

포함 관계                         값의 소유 관계
 
React Native 화면                 RN state ── RN JavaScript 런타임
└─ WebView 호스트 UI              WebView props ── React Native
   └─ HTML 문서                   Window·DOM·webCount ── 웹 페이지

여기서 “별도 실행환경”을 항상 “운영체제 프로세스가 반드시 하나 더 생긴다”로 바꾸어 외우면 안 된다. 프로세스 배치는 플랫폼과 버전에 따라 달라질 수 있다. 이 글의 안정적인 계약은 전역 객체와 상태를 자동 공유하지 않는다는 점이다.

React Render와 웹 문서 실행을 한 상태 전이로 보지 않는다

React state가 바뀌면 App은 다시 Render되고 새 WebView props를 계산할 수 있다. 하지만 페이지의 script가 다시 실행되는 조건은 문서를 새로 불러오거나 WebView 통신 API를 호출하는 것처럼 별도로 정해진다.

RN setOrderId('order-202')
  → React Render
  → RN Text와 WebView props 계산
  ≠ 기존 페이지의 globalThis.orderId에 자동 대입

따라서 다음 두 질문을 PR에서 분리한다.

질문소유 영역이 글의 관찰
앱 화면에 어떤 WebView를 둘까?React Native컴포넌트와 props로 결정
현재 웹 문서의 전역 값은 무엇인가?웹 페이지페이지 script와 명시적 입력으로 결정

첫 page load의 공개 입력을 명시적으로 넘긴다

이 fixture의 orderId는 비밀값이 아닌 화면 식별자다. 새 페이지를 불러올 때 필요한 초기 입력이라는 계약을 코드로 드러낸다.

14.0.1 Reference에는 페이지 안에 포함된 별도 웹 문서 영역까지 값을 읽는다고 쓰여 있지만, 고정한 Apple·Android 구현에서 같은 방식으로 보장되는 계약은 아니다. 따라서 이 글은 WebView가 직접 불러온 가장 바깥 문서가 초기값을 읽는 동작만 양 플랫폼에서 검증한다. 포함된 다른 문서도 직접 읽는다고 가정하지 않는다.

그렇더라도 주 문서와 그 안에서 실행되는 제3자 script는 초기값을 읽을 수 있다. token이나 secret을 이 fixture처럼 넘기지 않고, 인증 정보의 소유권은 17편에서 별도로 판단한다.

import React, { useState } from 'react'
import { Button, SafeAreaView, StyleSheet, Text } from 'react-native'
import { WebView } from 'react-native-webview'
 
const GOOD_HTML = `<!doctype html>
<html lang="ko">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <body>
    <p id="web-order">웹 orderId: 읽는 중</p>
    <p>웹 카운터: <strong id="web-count">0</strong></p>
    <button id="web-plus">웹 카운터 +1</button>
    <script>
      window.addEventListener('load', () => {
        const rawInput = window.ReactNativeWebView.injectedObjectJson()
        const input = JSON.parse(rawInput)
        let webCount = 0
 
        document.querySelector('#web-order').textContent =
          '웹 orderId: ' + String(input.orderId)
        document.querySelector('#web-plus').addEventListener('click', () => {
          webCount += 1
          document.querySelector('#web-count').textContent = String(webCount)
        })
      })
    </script>
  </body>
</html>`
 
export default function App() {
  const [orderId, setOrderId] = useState('order-101')
 
  return (
    <SafeAreaView style={styles.screen}>
      <Text>RN orderId: {orderId}</Text>
      <Button title="RN 주문을 202로 변경" onPress={() => setOrderId('order-202')} />
      <WebView
        key={orderId}
        originWhitelist={['*']} // navigation 없는 인라인 fixture 전용
        source={{ html: GOOD_HTML }}
        injectedJavaScriptObject={{ orderId }}
        style={styles.webview}
      />
    </SafeAreaView>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12 },
  webview: { flex: 1, borderWidth: 1 },
})

첫 코드를 관찰한 뒤 두 번째 코드로 교체한다. Fast Refresh는 React state를 보존할 수 있으므로 그것만 기다리지 않는다. 양 플랫폼의 개발자 메뉴에서 Reload를 실행하거나 앱을 종료하고 다시 시작해 RN orderId: order-101을 확인한 뒤 같은 순서를 실행한다.

첫 page load
RN orderId: order-101
웹 orderId: order-101
웹 카운터: 0
 
웹 카운터 +1 뒤
RN orderId: order-101
웹 orderId: order-101
웹 카운터: 1
 
RN 주문을 202로 변경한 뒤 새 page load
RN orderId: order-202
웹 orderId: order-202
웹 카운터: 0

마지막 0이 중요하다. 앱 state가 페이지에 “공유”된 것이 아니라, 이전 웹 문서를 버리고 명시적 입력이 있는 새 문서를 만들었다. 기존 페이지의 webCount도 함께 사라졌다.

초기 입력과 실행 중 동기화를 분리한다

이 교정은 새 page load가 제품 요구와 일치할 때만 적절하다. 이미 작성 중인 웹 폼이나 결제 흐름을 유지해야 하는데 key로 WebView를 다시 만들면 페이지의 메모리 상태와 탐색 위치를 잃을 수 있다.

요구선택할 경계
새 문서가 열릴 때 공개 초기값 하나가 필요함load 입력을 명시하고 새 문서에서 읽음
웹의 버튼 사건을 앱이 알아야 함13편의 WebView → RN 메시지 계약
실행 중인 웹에 앱 명령을 보내야 함14편의 RN → WebView 명령 계약
요청과 응답 여러 개를 연결해야 함15편의 request ID 계약

injectedJavaScriptObject를 썼다는 사실만으로 live sync, 전달 성공 확인이나 양방향 통신이 생기지는 않는다. 전달 시점과 소유자를 먼저 정하고 그 요구에 맞는 경계를 고른다.

앱 식별자와 웹 출처를 같은 경계로 보지 않는다

예를 들어 https://shop.example/receipthttps://account.example/은 같은 앱 WebView에서 열어도 host가 달라 서로 다른 출처다. 반대로 같은 출처라는 사실도 페이지 내용 전체가 신뢰된다는 뜻은 아니다. navigation, 즉 다른 URL이나 문서로 이동하는 경로와 포함된 하위 문서, 제3자 script를 따로 확인해야 한다.

이 글의 정적 HTML fixture는 실행환경 분리만 증명한다. 실제 HTTPS 페이지의 cookie, 웹 저장소, 로그인 복구와 출처별 정책은 이 fixture로 증명되지 않는다. 그 경계는 17~19편에서 실제 URL과 서버 응답을 사용해 검증한다.

iOS와 Android에서 무엇을 각각 확인할까

공통 원리는 같지만 플랫폼 구현의 성공을 한쪽 결과로 대신할 수 없다.

관찰Android emulatoriOS Simulator실기기 필요 조건
정적 HTML load확인확인보통 불필요
RN 값과 page 전역 분리확인확인보통 불필요
초기 공개 orderId 주입확인확인보통 불필요
WebView 제거 뒤 웹 카운터 초기화확인확인보통 불필요
실제 결제·camera·파일·인증 UI가상 환경 제공 범위 확인가상 환경 제공 범위 확인실제 장치 능력이 판단 대상이면 필요

Android 성공은 iOS WKWebView 성공을 증명하지 않고, 그 반대도 마찬가지다. 이 글의 fixture는 두 가상 환경에서 닫을 수 있다. 실제 하드웨어 기능과 운영체제 인증 화면이 포함되면 지원 실기기에서 그 경계를 추가 검증한다.

pull request에서 달라져야 할 행동

WebView가 들어간 변경에서는 먼저 다음 표를 채운다.

RN 소유      orderId, 화면 선택, WebView mount 여부
웹 소유      Window, DOM, 웹 폼과 페이지 내부 상태
초기 입력    새 page load 때 필요한 공개 값
실행 중 통신 방향  Web→RN / RN→Web / 양방향
웹 출처      scheme + host + port, 허용할 navigation
민감도       주 문서와 그 안의 script가 읽어도 되는 값인지
검증         Android / iOS / 필요한 실기기 능력

검토자는 다음을 확인한다.

  • React state 이름을 페이지 전역 변수처럼 가정하지 않았는가?
  • 초기 입력과 실행 중 메시지를 구분했는가?
  • WebView reload가 페이지 상태를 버려도 되는 요구인가?
  • token·secret을 페이지 script가 읽는 초기 입력으로 넘기지 않았는가?
  • 앱 내부라는 이유만으로 페이지 URL과 navigation을 신뢰하지 않았는가?
  • Android와 iOS에서 실제로 load·입력·재생성 결과를 각각 확인했는가?

한 장으로 다시 보기

문제       RN orderId가 WebView의 globalThis.orderId에도 있다고 가정
관찰       RN은 order-101, 웹은 undefined, 웹 카운터는 독립적으로 증가
원인       WebView page가 별도 Window·JavaScript Realm과 상태를 소유
행동       공개 초기값을 명시적 load 입력으로 전달하고 소유 경계를 기록
검증       양 플랫폼에서 101 일치 → 웹 count 1 → RN 202 → 새 웹 202·count 0
경계       이 결과는 live sync·메시지 성공·cookie·인증 복구를 증명하지 않음

WebView는 React Native 컴포넌트이면서 동시에 웹 문서를 담는 플랫폼 UI다. 이 두 사실을 합쳐서 “같은 JavaScript state를 공유한다”고 결론 내리면 안 된다. 앱과 페이지가 무엇을 각각 소유하고 어떤 입구로 값을 넘겼는지를 코드와 검증 결과에 드러내는 것이 이 글의 핵심이다.

스스로 확인할 질문

  1. WebView가 React Native 트리 안에 있어도 페이지 전역 변수를 공유하지 않는 이유는 무엇인가?
  2. React Native Render와 웹 문서 script 실행은 어떤 조건에서 각각 일어나는가?
  3. key={orderId} 교정에서 웹 카운터가 0으로 돌아가는 이유는 무엇인가?
  4. 초기 입력과 실행 중 메시지를 왜 같은 계약으로 보면 안 되는가?
  5. 앱 package name과 HTTPS 페이지의 웹 출처는 어떻게 다른가?

Active recall

기억에서 꺼내 보기

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

  1. 01WebView가 React Native 컴포넌트 트리 안에 렌더되면 페이지 JavaScript에서 React Native state 이름을 바로 읽을 수 있을까?

    정답

    아니다. WebView는 네이티브 화면 안에 들어가지만, 불러온 페이지는 별도 Window와 JavaScript Realm을 사용한다. 값은 합의한 입력이나 메시지 경로로 옮겨야 한다.

    왜 그런가

    화면의 포함 관계와 JavaScript 변수의 소유 범위는 서로 다른 계약이다.

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

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

  2. 02React Native state가 바뀌어 부모 컴포넌트가 다시 Render되면 WebView 페이지의 같은 이름 전역 변수도 갱신될까?

    정답

    아니다. React Render는 WebView props를 다시 계산할 수 있지만 페이지 전역 값을 자동으로 쓰지 않는다. 이 글의 교정은 새 WebView load에 초기 입력을 명시적으로 제공한다.

    왜 그런가

    React Native의 Render와 웹 문서의 script 실행은 서로 다른 상태 전이이다.

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

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

  3. 03injectedJavaScriptObject로 처음 값을 전달했으면 이후 양방향 상태 동기화도 해결된 걸까?

    정답

    아니다. 이 글은 새 page load의 초기 입력만 증명한다. 페이지 사건을 앱으로 보내는 일과 실행 중 앱 명령을 페이지로 보내는 일은 각각 별도 메시지 계약이 필요하다.

    왜 그런가

    초기화와 실행 중 통신은 전달 시점과 실패 조건이 다르다.

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

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

  4. 04앱이 소유한 WebView에서 열었으니 모든 웹 주소를 앱과 같은 신뢰 경계로 봐도 될까?

    정답

    아니다. 페이지에는 URL에서 정해지는 웹 출처가 있고, 다른 URL로의 이동과 포함된 하위 문서도 별도로 검토해야 한다. 앱이 WebView를 소유한다는 사실만으로 페이지가 신뢰되지는 않는다.

    왜 그런가

    네이티브 앱 식별자와 웹의 scheme·host·port 출처는 다른 분류 체계다.

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

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

출처와 검증 범위

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

  1. React Native WebView 14.0.1 package metadataReact Native WebView · 소스 코드 · 확인 2026-08-09
  2. React Native WebView 14.0.1 Getting StartedReact Native WebView · 공식 문서 · 확인 2026-08-09
  3. React Native WebView 14.0.1 ReferenceReact Native WebView · 공식 문서 · 확인 2026-08-09
  4. React Native WebView 14.0.1 Apple implementationReact Native WebView · 소스 코드 · 확인 2026-08-09
  5. React Native WebView 14.0.1 Android implementationReact Native WebView · 소스 코드 · 확인 2026-08-09
  6. React Native 0.86.2 releaseReact Native · 공식 문서 · 확인 2026-08-09
  7. Get Started Without a FrameworkReact Native · 공식 문서 · 확인 2026-08-09
  8. WKWebViewApple Developer · 공식 문서 · 확인 2026-08-09
  9. Build web apps in WebViewAndroid Developers · 공식 문서 · 확인 2026-08-09
  10. HTML Standard — Realms and their counterpartsWHATWG · 표준 · 확인 2026-08-09
  11. HTML Standard — The Window objectWHATWG · 표준 · 확인 2026-08-09
  12. ECMAScript Language Specification — globalThisEcma International · 표준 · 확인 2026-08-09
  13. HTML Standard — WindowProxy exotic objectsWHATWG · 표준 · 확인 2026-08-09
  14. HTML Standard — OriginsWHATWG · 표준 · 확인 2026-08-09
  15. Extended controls, settings, and helpAndroid Developers · 공식 문서 · 확인 2026-08-09
  16. Running your app on simulated or physical devicesApple Developer · 공식 문서 · 확인 2026-08-09
  17. Preserving and Resetting StateReact · 공식 문서 · 확인 2026-08-09
  18. Fast RefreshReact Native · 공식 문서 · 확인 2026-08-09
  19. Debugging — Accessing the Dev MenuReact Native · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.