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

WebView에서 React Native로 보내는 메시지: 웹의 값을 어디에서 받는가

WebView 페이지의 송신 함수와 React Native의 onMessage를 한 쌍으로 연결하고, 문자열 수신과 입력 신뢰를 분리한다.

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

30초 요약

WebView 페이지에서 React Native로 값을 보내려면 양쪽 입구를 함께 만든다.

웹 문서
  window.ReactNativeWebView.postMessage(문자열)
    → WebView의 onMessage(event)
      → event.nativeEvent.data
        → React Native가 받은 원문 문자열

onMessage가 없으면 페이지 송신 함수가 제공된다고 기대할 수 없다. 값을 받았더라도 그것은 아직 원문 문자열이다. JSON 해석, 구조 검사와 허용할 앱 동작은 뒤 단계다.

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

웹의 송신 함수와 앱의 수신 함수를 한 쌍으로 만들고, 도착과 신뢰를 분리한다.

이 글은 WebView → React Native 한 방향의 수신 입구까지만 다룬다. React Native → WebView 명령은 14편, 여러 메시지의 요청·응답 연결은 15편, 입력 구조 검사는 16편에서 각각 다룬다.

이 글을 관통하는 상황: 웹 버튼은 눌렸는데 앱에는 아무 메시지도 없다

12편의 WebBoundaryApp을 그대로 이어 쓴다. React Native 0.86.2와 react-native-webview 14.0.1이 설치된 상태에서 App.tsx만 교체한다. 먼저 페이지에 송신 버튼은 만들지만 WebViewonMessage는 설정하지 않은 실패를 관찰한다.

다음은 실패를 재현하는 완결 App.tsx다.

import React from 'react'
import { 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>
    <button id="open-receipt">영수증을 앱에서 보기</button>
    <p id="web-status">웹 상태: 아직 누르지 않음</p>
    <script>
      document.querySelector('#open-receipt').addEventListener('click', () => {
        const appEntry = window.ReactNativeWebView
 
        if (!appEntry || typeof appEntry.postMessage !== 'function') {
          document.querySelector('#web-status').textContent =
            '웹 상태: 앱 메시지 송신 함수 없음'
          return
        }
 
        appEntry.postMessage('receipt.opened')
        document.querySelector('#web-status').textContent =
          '웹 상태: 전송 요청 완료'
      })
    </script>
  </body>
</html>`
 
export default function App() {
  return (
    <SafeAreaView style={styles.screen}>
      <Text>RN 마지막 메시지: 없음</Text>
      <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. 웹 버튼을 누르면 웹 상태는 전송 요청 완료일까, 앱 메시지 송신 함수 없음일까?
  2. React Native의 마지막 메시지는 바뀔까?

Android emulator와 iOS Simulator에서 버튼을 한 번 누르면 다음 결과가 남는다.

웹 상태: 앱 메시지 송신 함수 없음
RN 마지막 메시지: 없음

버튼 사건은 웹 문서 안에서 실행됐다. 그러나 앱이 onMessage 수신 함수를 제공하지 않았으므로 이 fixture에는 앱 방향 postMessage 송신 함수가 없다. 페이지가 앱 컴포넌트 안에 보인다는 사실만으로 React Native state를 바꾸는 통로가 생기지는 않는다.

송신 함수와 수신 함수를 한 쌍으로 연결한다

페이지와 앱의 책임을 먼저 나누면 코드가 단순해진다.

위치책임이 글에서 확인할 값
웹 문서보낼 사건을 문자열로 만든 뒤 송신 함수를 호출receipt.opened, order-101, 전송 횟수
WebView 경계페이지 메시지를 앱 사건으로 전달onMessage 호출
React Native사건 인자의 원문 문자열을 읽어 관찰 가능한 state에 기록event.nativeEvent.data

여기서 eventonMessage가 받은 사건 인자다. 그 안의 nativeEvent는 WebView가 전달한 사건 정보이고, data가 페이지가 보낸 문자열이다.

다음 코드로 첫 App.tsx 전체를 교체한다.

import React, { useState } from 'react'
import { 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>
    <button id="open-receipt">영수증을 앱에서 보기</button>
    <p id="web-status">웹 상태: 아직 누르지 않음</p>
    <script>
      let sentCount = 0
 
      document.querySelector('#open-receipt').addEventListener('click', () => {
        sentCount += 1
 
        const message = {
          type: 'receipt.opened',
          orderId: 'order-101',
          sentCount,
        }
 
        window.ReactNativeWebView.postMessage(JSON.stringify(message))
        document.querySelector('#web-status').textContent =
          '웹 상태: 전송 요청 ' + String(sentCount) + '회'
      })
    </script>
  </body>
</html>`
 
export default function App() {
  const [receivedCount, setReceivedCount] = useState(0)
  const [lastRawMessage, setLastRawMessage] = useState('없음')
 
  return (
    <SafeAreaView style={styles.screen}>
      <Text>RN 수신 횟수: {receivedCount}</Text>
      <Text>RN 마지막 원문: {lastRawMessage}</Text>
      <WebView
        originWhitelist={['*']} // navigation 없는 인라인 fixture 전용
        source={{ html: GOOD_HTML }}
        onMessage={(event) => {
          setReceivedCount((count) => count + 1)
          setLastRawMessage(event.nativeEvent.data)
        }}
        style={styles.webview}
      />
    </SafeAreaView>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12 },
  webview: { flex: 1, borderWidth: 1 },
})

객체를 공유하지 않고 문자열을 전달한다

웹의 message 객체와 React Native 코드가 같은 객체 참조를 공유하는 것은 아니다.

웹 객체
  { type, orderId, sentCount }
    → JSON.stringify
      → '{"type":"receipt.opened","orderId":"order-101","sentCount":1}'
        → WebView 메시지 경계
          → event.nativeEvent.data 문자열

두 실행 영역은 문자열 형식에만 합의했다. 이 글의 앱은 아직 JSON.parse하지 않는다. 우선 전달된 원문이 무엇인지 화면에 그대로 보여 주어 운반 경로 하나만 검증한다.

두 번째 코드로 교체한 뒤 Reload 또는 앱 재시작으로 기준선을 초기화한다. 처음 화면이 아래와 같은지 확인한다.

웹 상태: 아직 누르지 않음
RN 수신 횟수: 0
RN 마지막 원문: 없음

웹 버튼을 두 번 누른다.

첫 번째 누름
웹 상태: 전송 요청 1회
RN 수신 횟수: 1
RN 마지막 원문: {"type":"receipt.opened","orderId":"order-101","sentCount":1}
 
두 번째 누름
웹 상태: 전송 요청 2회
RN 수신 횟수: 2
RN 마지막 원문: {"type":"receipt.opened","orderId":"order-101","sentCount":2}

이 결과로 증명한 것은 세 가지뿐이다.

  1. onMessage가 있을 때 페이지 송신 함수가 존재한다.
  2. 웹 버튼 한 번마다 앱 수신 함수가 호출된다.
  3. 페이지가 만든 JSON 문자열이 event.nativeEvent.data에 도착한다.

웹 화면의 전송 요청 문구만 보고 앱 수신 성공이라고 결론 내리지 않는다. 앱 화면의 수신 횟수와 원문 문자열을 함께 확인해야 이 방향의 전달을 관찰할 수 있다.

이름이 비슷한 브라우저 메시지와 구분한다

따라서 코드를 검토할 때 메서드 이름 끝부분만 보지 않는다.

window.postMessage(...)
  → 웹 Window 사이의 표준 메시지
 
window.ReactNativeWebView.postMessage(...)
  → WebView 페이지에서 React Native onMessage로 보내는 메시지

페이지 개발자가 익숙한 브라우저 API를 호출했다고 해서 React Native onMessage가 받는 것은 아니다. 어느 객체가 제공한 함수인지까지 포함한 전체 API 경로를 확인한다.

도착과 신뢰를 분리한다

이 글의 GOOD_HTML은 앱에 포함한 고정 fixture라 메시지 운반만 관찰하기 쉽다. 실제 URL에서는 페이지 자체 script뿐 아니라 그 문서에 포함된 script도 송신 함수에 접근할 수 있다. 그러므로 수신 직후 다음 동작을 바로 실행하는 코드는 이 글의 교정이 아니다.

onMessage={(event) => {
  // 잘못된 확장: 원문이 특정 문자열이라는 이유만으로 권한 동작을 즉시 실행
  if (event.nativeEvent.data.includes('camera')) {
    openCamera()
  }
}}

includes는 문자열 일부가 있다는 사실만 볼 뿐 메시지 구조와 허용된 명령을 증명하지 않는다. 이 글은 원문 기록에서 멈춘다. 16편에서 JSON 해석 실패, 필드 구조와 허용 목록을 하나씩 검사한 뒤 앱 동작으로 넘기는 경계를 만든다.

한 번 보냈다는 사실과 처리 완료를 합치지 않는다

페이지의 postMessage 호출은 이 방향의 전송을 요청한다. 그것만으로 앱이 어떤 업무를 완료했는지 페이지가 알 수 있는 응답 계약은 생기지 않는다.

페이지: receipt.opened 전송 요청
앱:     원문 문자열 수신
 
아직 없는 것
- 앱이 내용을 해석했다는 응답
- 화면 이동이 완료됐다는 응답
- 여러 요청 중 어느 응답인지 연결하는 식별자

앱에서 페이지로 값을 보내는 반대 방향은 14편에서 만든다. 두 방향이 생긴 뒤 여러 요청과 응답을 연결하는 식별자는 15편에서 추가한다. 이 순서를 지켜야 postMessage 한 번을 양방향 호출처럼 과대해석하지 않는다.

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

관찰Android emulatoriOS Simulator실기기 조건
onMessage 없음과 송신 함수 부재확인확인보통 불필요
버튼 2회와 RN 수신 2회확인확인보통 불필요
마지막 JSON 원문 문자열확인확인보통 불필요
실제 장치 기능을 실행하는 후속 동작가상 환경 제공 범위가상 환경 제공 범위장치 능력이 판단 대상이면 필요

한 플랫폼에서 메시지가 왔다고 다른 플랫폼의 전달도 성공했다고 기록하지 않는다. 동일한 App.tsx를 양쪽에서 실행하고 수신 횟수와 마지막 원문을 각각 남긴다.

pull request에서 달라져야 할 행동

WebView → React Native 메시지를 추가할 때 다음 경계를 먼저 적는다.

송신 주체       어느 웹 문서와 script가 보내는가
송신 API         window.ReactNativeWebView.postMessage
문자열 형식      type, 필요한 식별자, 버전 여부
수신 입구        어느 WebView의 onMessage인가
수신 증거        사건 횟수와 event.nativeEvent.data 원문
후속 행동        아직 없음 / 검증 뒤 허용할 동작
양방향 필요      응답이 필요한가, request ID가 필요한가
검증 환경        Android / iOS / 필요한 실기기 기능

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

  • onMessage 없이 페이지 송신 함수를 가정하지 않았는가?
  • 표준 window.postMessage와 WebView 제공 API를 혼동하지 않았는가?
  • 객체를 직접 공유한다고 가정하지 않고 문자열 형식을 정했는가?
  • 앱 수신 결과를 페이지의 전송 요청 문구만으로 대신하지 않았는가?
  • 원문 수신과 구조 검사·권한 동작을 분리했는가?
  • Android와 iOS에서 같은 사건 횟수와 원문을 각각 확인했는가?

한 장으로 다시 보기

문제       웹 버튼은 눌렸지만 앱의 마지막 메시지는 계속 없음
관찰       onMessage가 없으면 페이지에 앱 메시지 송신 함수가 없음
원인       페이지 송신 함수와 앱 수신 함수를 한 쌍으로 만들지 않음
행동       JSON 문자열 송신 → onMessage → event.nativeEvent.data 기록
검증       양 플랫폼에서 버튼 2회 → RN 수신 2회 → 마지막 sentCount 2
경계       원문 도착은 구조·신뢰·업무 처리 완료를 증명하지 않음

WebView 메시지는 같은 메모리를 공유하는 지름길이 아니다. 페이지와 앱이 서로 다른 실행 영역이라는 12편의 결론 위에 명시적인 문자열 입구를 하나 더한 것이다. 어떤 함수가 보내고 어디에서 받으며, 도착 뒤 아직 무엇을 믿지 않는지까지 기록해야 실무에서 재현 가능한 계약이 된다.

스스로 확인할 질문

  1. onMessage가 없을 때 페이지 송신 함수를 기대할 수 없는 이유는 무엇인가?
  2. 앱이 실제로 받은 원문 문자열은 어느 속성에서 읽는가?
  3. 웹 객체를 JSON.stringify하는 이유는 무엇인가?
  4. 표준 window.postMessage와 WebView의 송신 API는 어떻게 다른가?
  5. 원문 문자열을 받았다는 사실만으로 앱 권한 동작을 실행하면 안 되는 이유는 무엇인가?

Active recall

기억에서 꺼내 보기

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

  1. 01WebView 페이지가 window.ReactNativeWebView.postMessage를 호출하기만 하면 앱에 수신 입구가 없어도 메시지가 도착할까?

    정답

    아니다. WebView에 onMessage를 설정해야 페이지 송신 함수가 제공되고, 앱은 그 onMessage에서 event.nativeEvent.data를 읽는다.

    왜 그런가

    페이지 송신 API와 앱 수신 함수를 한 쌍의 경계로 구성해야 한다.

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

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

  2. 02페이지의 JavaScript 객체를 postMessage 인자에 그대로 넣으면 앱이 같은 객체를 받는 계약일까?

    정답

    아니다. 이 API의 전달값은 문자열이다. 구조 있는 값은 JSON.stringify로 문자열을 만들고 앱은 우선 event.nativeEvent.data라는 원문 문자열로 받는다.

    왜 그런가

    객체의 메모리 표현을 공유하는 것이 아니라 두 실행 영역이 문자열 경계를 합의한다.

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

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

  3. 03onMessage가 호출되고 JSON처럼 보이는 문자열을 받았으면 바로 화면 이동이나 권한 동작을 실행해도 될까?

    정답

    아니다. 이 글이 증명하는 것은 문자열이 수신 입구에 도착했다는 사실뿐이다. 해석·구조 검사·보낸 페이지의 신뢰·허용할 동작은 별도로 확인해야 한다.

    왜 그런가

    운반 성공과 입력 신뢰는 서로 다른 계약이다.

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

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

  4. 04표준 브라우저의 window.postMessage와 window.ReactNativeWebView.postMessage는 이름이 비슷하므로 같은 수신 함수를 사용할까?

    정답

    아니다. 앞의 API는 웹 문서 사이의 표준 메시지이고, 뒤의 API는 React Native WebView가 페이지에 제공하는 앱 방향 입구다.

    왜 그런가

    어느 객체가 제공한 함수인지까지 포함해 API 이름을 읽어야 한다.

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

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

출처와 검증 범위

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

  1. React Native WebView 14.0.1 package metadataReact Native WebView · 소스 코드 · 확인 2026-08-09
  2. React Native WebView 14.0.1 ReferenceReact Native WebView · 공식 문서 · 확인 2026-08-09
  3. React Native WebView 14.0.1 GuideReact Native WebView · 공식 문서 · 확인 2026-08-09
  4. React Native WebView 14.0.1 WebView event typesReact Native WebView · 소스 코드 · 확인 2026-08-09
  5. ECMAScript Language Specification — JSON.stringifyEcma International · 표준 명세 · 확인 2026-08-09
  6. HTML Standard — Cross-document messagingWHATWG · 표준 · 확인 2026-08-09
  7. HTML Standard — Realms and their counterpartsWHATWG · 표준 · 확인 2026-08-09
  8. Fast RefreshReact Native · 공식 문서 · 확인 2026-08-09
  9. Debugging — Accessing the Dev MenuReact Native · 공식 문서 · 확인 2026-08-09
  10. Extended controls, settings, and helpAndroid Developers · 공식 문서 · 확인 2026-08-09
  11. Running your app on simulated or physical devicesApple Developer · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.