简体   繁体   English

React Native webview路由

[英]React Native webview routing

I'm actually building a React-Native app, with some native screens, and some screen where I load the website with a webview.我实际上正在构建一个 React-Native 应用程序,其中包含一些本机屏幕和一些我使用 web 视图加载网站的屏幕。 Instead of the classical website navigation, I have a native drawer allowing me to switch pages.代替经典的网站导航,我有一个原生抽屉,允许我切换页面。 My issue is that the website is using react-router, so it handles smoothly the url change on browsers by loading only the necessary code, but when I change the url in my webview, it does as if I was refreshing the website, and it reloads everything, leading up to a very slow navigation.我的问题是该网站正在使用 react-router,因此它通过仅加载必要的代码来顺利处理浏览器上的 url 更改,但是当我在 webview 中更改 url 时,它就像我正在刷新网站一样重新加载所有内容,导致导航非常缓慢。

The only 'hack' I tought of would be exposing a function on the website window variable to trigger a react-router 'go to'.我想到的唯一“黑客”是在网站window变量上公开一个函数以触发反应路由器“转到”。

Any ideas ?有任何想法吗 ?

您是否尝试过UseEffectroute navigation的组合?

So, after a few days of struggling, I have settled with the following solution, which I admit is slighlty obscure, but works perfectly (incl. back button, gestures):因此,经过几天的挣扎,我已经解决了以下解决方案,我承认这有点晦涩,但效果很好(包括后退按钮、手势):

In the React Web Application , I keep the ReactRouter history object available through the global Window object.React Web Application 中,我通过全局 Window 对象保持 ReactRouter 历史对象可用。

import { useHistory } from 'react-router'
// ...

declare global {
  interface Window {
    ReactRouterHistory: ReturnType<typeof useHistory>
  }
}

const AppContextProvider = props => {
  const history = useHistory()

  // provide history object globally for use in Mobile Application
  window.ReactRouterHistory = history

  // ...
}

In the React Native Mobile Application , I have custom code injected to the WebView, that makes use of the history object for navigation and the application communicates with this code using messages:React Native Mobile Application 中,我将自定义代码注入到 WebView,它利用历史对象进行导航,并且应用程序使用消息与此代码进行通信:

webViewScript.ts

// ...
export default `
  const handleMessage = (event) => {
    var message = JSON.parse(event.data)
    switch (message.type) {
      // ...
      case '${MessageTypes.NAVIGATE}':
        if (message.params.uri && message.params.uri.match('${APP_URL}') && window.ReactRouterHistory) {
          const url = message.params.uri.replace('${APP_URL}', '')
          window.ReactRouterHistory.push(url)
        }
        break
    }
  };

  !(() => {
    function sendMessage(type, params) {
      var message = JSON.stringify({type: type, params: params})
      window.ReactNativeWebView.postMessage(message)
    }

    if (!window.appListenersAdded) {
      window.appListenersAdded = true;
    
      window.addEventListener('message', handleMessage)
      
      var originalPushState = window.history.pushState
      window.history.pushState = function () {
        sendMessage('${MessageTypes.PUSH_STATE}', {state: arguments[0], title: arguments[1], url: arguments[2]})
        originalPushState.apply(this, arguments)
      }

    }
  })()
  true
`

intercom.ts (no routing specifics here, just for generic communication) intercom.ts (此处没有路由细节,仅用于通用通信)

import WebView, {WebViewMessageEvent} from 'react-native-webview'
import MessageTypes from './messageTypes'


export const sendMessage = (webview: WebView | null, type: MessageTypes, params: Record<string, unknown> = {}) => {
  const src = `
  window.postMessage('${JSON.stringify({type, params})}', '*')
  true // Might fail silently per documentation
  `
  if (webview) webview.injectJavaScript(src)
}

export type Message = {
  type?: MessageTypes,
  params?: Record<string, unknown>
}

export type MessageHandler = (message: Message) => void

export const handleMessage = (handlers: Partial<Record<MessageTypes, MessageHandler>>) => (
  (event: WebViewMessageEvent) => {
    const message = JSON.parse(event.nativeEvent.data) as Message
    const messageType = message.type

    if (!messageType) return
    const handler = handlers[messageType]
  
    if (handler) handler(message)
  }
)

export {default as script} from './webViewScript'

WebViewScreen.tsx

import {handleMessage, Message, script, sendMessage} from '../intercom'
// ...

const WebViewScreen = ({navigation, navigationStack}: WebViewScreenProps) => {
  const messageHandlers = {
    [MessageTypes.PUSH_STATE]: ({params}: Message) => {
      if (!params) return
      const {url} = params
      const fullUrl = `${APP_URL}${url}`
      navigationStack.push(fullUrl)
    },
    // ...
  }

  const uri = navigationStack.currentPath

  // navigation solution using history object propagated through window object
  useEffect(() => {
    if (uri) {
      sendMessage(webViewRef.current, MessageTypes.NAVIGATE, {uri})
    }
  }, [uri])

  // this is correct! Source is never going to be updated, navigation is done using native history object, see useEffect above
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const source = useMemo(() => ({uri}), [])

  return (
    <View
        // ...
        refreshControl={
          <RefreshControl
              // ...
          />
        }
    >
      <WebView
          source={source}
          injectedJavaScript={script}
          onMessage={handleMessage(messageHandlers)}
          // ...
      />
    </View>
  )

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM