ghdtjgus

이번에는 RNCWebViewManagerImpl 파일을 좀 더 자세히 살펴보자.

 

이전 글에서 설명했다시피 RNCWebViewManager에서는 UI와 관련된 기능 및 웹뷰 개별 인스턴스로 관리해야 하는 기능들이 들어가 있다고 했다.

그래서 우리가 웹뷰 컴포넌트를 사용하며 너무나 자주 사용하는 injectJavascript, postMessage, onMessage와 같은 기능들이 다 여기 들어가 있는 것이다.

 

우리가 알아볼 코드는 아래 링크를 접속하면 볼 수 있다.

https://github.com/react-native-webview/react-native-webview/blob/master/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt

 

일단 먼저 알아야 하는 개념이 있다.

어떻게 react native WebView 컴포넌트에서 메서드를 호출하면 네이티브에서도 동작할지? 내부 로직에 대해서 알아보자.

 

다시 WebView.android.tsx 파일로 돌아가자.

https://github.com/react-native-webview/react-native-webview/blob/master/src/WebView.android.tsx

 

아래 파일에서 주목해야 할 부분은 다음과 같다.

useImperativeHandle 훅은 다 알겠지만, ref를 사용할 때 부모 컴포넌트가 호출할 수 있는 메서드만을 정의해 두는 것이다.

훅 하나하나를 살펴보면 Commands라는 걸 사용한다.

useImperativeHandle(
  ref,
  () => ({
    goForward: () =>
      webViewRef.current && Commands.goForward(webViewRef.current),
    goBack: () => webViewRef.current && Commands.goBack(webViewRef.current),
      reload: () => {
        setViewState('LOADING');
        if (webViewRef.current) {
          Commands.reload(webViewRef.current);
        }
      },
    stopLoading: () =>
      webViewRef.current && Commands.stopLoading(webViewRef.current),
    postMessage: (data: string) =>
      webViewRef.current && Commands.postMessage(webViewRef.current, data),
    injectJavaScript: (data: string) =>
      webViewRef.current &&
        Commands.injectJavaScript(webViewRef.current, data),
    requestFocus: () =>
      webViewRef.current && Commands.requestFocus(webViewRef.current),
    clearFormData: () =>
      webViewRef.current && Commands.clearFormData(webViewRef.current),
    clearCache: (includeDiskFiles: boolean) =>
      webViewRef.current &&
        Commands.clearCache(webViewRef.current, includeDiskFiles),
    clearHistory: () =>
      webViewRef.current && Commands.clearHistory(webViewRef.current),
    }),
    [setViewState, webViewRef]
  );
import RNCWebView, { Commands, NativeProps } from './RNCWebViewNativeComponent';
export const Commands = codegenNativeCommands<NativeCommands>({
  supportedCommands: [
    'goBack',
    'goForward',
    'reload',
    'stopLoading',
    'injectJavaScript',
    'requestFocus',
    'postMessage',
    'loadUrl',
    'clearFormData',
    'clearCache',
    'clearHistory',
  ],
});

 

codegenNativeCommands를 통해 명령들을 정의했고 해당 명령을 호출하면 네이티브 모듈로 명령이 전달된다.

 

받은 명령을 처리하는 건 또 네이티브 모듈 몫인데 아래 코드에서 receiveCommand 함수를 보면 된다.

https://github.com/react-native-webview/react-native-webview/blob/master/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java

https://github.com/react-native-webview/react-native-webview/blob/master/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt

@Override
public void receiveCommand(@NonNull RNCWebViewWrapper reactWebView, String commandId, @Nullable ReadableArray args) {
    mRNCWebViewManagerImpl.receiveCommand(reactWebView, commandId, args);
    super.receiveCommand(reactWebView, commandId, args);
}
fun receiveCommand(viewWrapper: RNCWebViewWrapper, commandId: String, args: ReadableArray) {
      val webView = viewWrapper.webView
      when (commandId) {
        "goBack" -> webView.goBack()
        "goForward" -> webView.goForward()
        "reload" -> webView.reload()
        "stopLoading" -> webView.stopLoading()
        "postMessage" -> try {
          val eventInitDict = JSONObject()
          eventInitDict.put("data", args.getString(0))
          webView.evaluateJavascriptWithFallback(
            "(function () {" +
              "var event;" +
              "var data = " + eventInitDict.toString() + ";" +
              "try {" +
              "event = new MessageEvent('message', data);" +
              "} catch (e) {" +
              "event = document.createEvent('MessageEvent');" +
              "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
              "}" +
              "document.dispatchEvent(event);" +
              "})();"
          )
        } catch (e: JSONException) {
          throw RuntimeException(e)
        }
        "injectJavaScript" -> webView.evaluateJavascriptWithFallback(args.getString(0))
        "loadUrl" -> {
          if (args == null) {
            throw RuntimeException("Arguments for loading an url are null!")
          }
          webView.progressChangedFilter.setWaitingForCommandLoadUrl(false)
          webView.loadUrl(args.getString(0))
        }
        "requestFocus" -> webView.requestFocus()
        "clearFormData" -> webView.clearFormData()
        "clearCache" -> {
          val includeDiskFiles = args != null && args.getBoolean(0)
          webView.clearCache(includeDiskFiles)
        }
        "clearHistory" -> webView.clearHistory()
      }
    }

 

여기서 받은 명령을 처리하게 되는데, 여기서의 동작 구조도 이전 글을 이해했으면 쉽게 이해할 수 있을 거 같다.

여기까지만 하면 진짜 얼마 안 남았다.

 

postMessage

"postMessage" -> try {
    val eventInitDict = JSONObject()
    eventInitDict.put("data", args.getString(0))
    webView.evaluateJavascriptWithFallback(
        "(function () {" +
        "var event;" +
        "var data = " + eventInitDict.toString() + ";" +
        "try {" +
        "event = new MessageEvent('message', data);" +
        "} catch (e) {" +
        "event = document.createEvent('MessageEvent');" +
        "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
        "}" +
        "document.dispatchEvent(event);" +
        "})();"
    )
} catch (e: JSONException) {
    throw RuntimeException(e)
}

receiveCommand 함수에서 postMessage라는 Command를 받게 되면 위 로직을 실행한다.

 

이는 네이티브 모듈에서 수신된 메시지를 웹뷰 내의 자바스크립트 코드로 전달하기 위한 코드이다.

postMessage 명령 수신 시 args 배열에서 메시지 데이터를 가져와서 eventInitDict에 저장한다.

이후 자바스크립트 코드 문자열을 생성해 evaluateJavascriptWithFallback 함수를 호출한다.

이 함수는 네이티브 측에서 자바스크립트 코드를 실행하기 위해서 호출한다.

자바스크립트 코드에서는 MessageEvent를 생성하고 이를 dispatch해서 웹뷰 내에서 message 이벤트를 트리거하게 된다.

 

사실 나머지 메서드들이야 뭐 위 내용들 다 이해했으면 문제 없이 이해될 거 같아서 생략해 버렸다.

profile

ghdtjgus

@gugu76

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그