250 行
5.4 KiB
Markdown
250 行
5.4 KiB
Markdown
# XWebView
|
||
|
||
`@xuqm/rn-xwebview` 提供可直接打开的独立 WebView 页面,也支持在业务页面中嵌入浏览器
|
||
内核。SDK 只提供通用容器和 Bridge 接入入口,不内置任何宿主业务协议。
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
pnpm add @xuqm/rn-common @xuqm/rn-xwebview \
|
||
react-native-webview react-native-svg \
|
||
@react-native-clipboard/clipboard react-native-safe-area-context
|
||
```
|
||
|
||
这些原生依赖需要由宿主直接声明,以便 React Native 自动链接。
|
||
|
||
## 根部挂载 Host
|
||
|
||
独立页面模式需要在应用根部挂载一次:
|
||
|
||
```tsx
|
||
import { XWebViewHost } from '@xuqm/rn-xwebview'
|
||
|
||
export function App() {
|
||
return (
|
||
<>
|
||
<RootNavigation />
|
||
<XWebViewHost />
|
||
</>
|
||
)
|
||
}
|
||
```
|
||
|
||
全局只能存在一个 `XWebViewHost`。
|
||
|
||
## 打开独立页面
|
||
|
||
```ts
|
||
import { openWebView } from '@xuqm/rn-xwebview'
|
||
|
||
const page = openWebView({
|
||
source: {
|
||
uri: 'https://example.com',
|
||
headers: {
|
||
'X-From-App': 'true',
|
||
},
|
||
},
|
||
navigationBar: {
|
||
title: {
|
||
text: '服务页面',
|
||
mode: 'web',
|
||
},
|
||
},
|
||
statusBar: {
|
||
translucent: true,
|
||
contentStyle: 'dark-content',
|
||
},
|
||
})
|
||
|
||
const result = await page.closed
|
||
console.log(result.reason, result.data)
|
||
```
|
||
|
||
返回的页面 Handle 可以:
|
||
|
||
```ts
|
||
page.reload()
|
||
page.goBack()
|
||
page.goForward()
|
||
page.postMessage(JSON.stringify({ type: 'refresh' }))
|
||
page.evaluateJavaScript('window.refreshPage?.()')
|
||
page.suspend()
|
||
page.resume()
|
||
page.close('api', { completed: true })
|
||
```
|
||
|
||
`suspend()` 用于临时展示宿主原生页面,页面实例和网页历史会保留;完成后调用
|
||
`resume()`。
|
||
|
||
## 默认导航和返回规则
|
||
|
||
- 左上角返回按钮只在当前网页存在历史页面时显示,只负责网页后退。
|
||
- 右上角关闭按钮始终显示,点击立即关闭当前 WebView 页面。
|
||
- Android 物理返回键优先返回网页历史。
|
||
- 没有网页历史时,物理返回键需要在一秒内再次按下才关闭页面。
|
||
- 默认菜单包含刷新、复制链接和在系统浏览器打开,不包含关闭页面。
|
||
|
||
## 导航栏与状态栏
|
||
|
||
```ts
|
||
openWebView({
|
||
source: { uri: 'https://example.com' },
|
||
navigationBar: {
|
||
background: {
|
||
gradient: ['#1769E0', '#55A8FF'],
|
||
gradientAngle: 90,
|
||
// 也可以使用 color 或 image
|
||
},
|
||
title: {
|
||
text: '渐变导航栏',
|
||
color: '#FFFFFF',
|
||
mode: 'fixed',
|
||
},
|
||
backButton: { color: '#FFFFFF' },
|
||
closeButton: { color: '#FFFFFF' },
|
||
},
|
||
statusBar: {
|
||
backgroundColor: '#1769E0',
|
||
contentStyle: 'light-content',
|
||
translucent: true,
|
||
},
|
||
})
|
||
```
|
||
|
||
网络背景图片异步加载,不会阻塞 `openWebView` 返回。
|
||
|
||
完整替换导航栏时使用 `navigationBar.render(context)`。自定义返回按钮应调用
|
||
`context.onBackPress()`,以保留统一的网页后退规则。
|
||
|
||
## 自定义菜单
|
||
|
||
```tsx
|
||
openWebView({
|
||
source: { uri: 'https://example.com' },
|
||
navigationBar: {
|
||
menu: {
|
||
visible: true,
|
||
items: [
|
||
{
|
||
id: 'feedback',
|
||
label: '问题反馈',
|
||
onPress({ navigationState }) {
|
||
openFeedback({ url: navigationState.url })
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
不需要菜单时设置 `menu.visible: false`。
|
||
|
||
## 摄像头和麦克风
|
||
|
||
宿主先声明系统权限,然后为具体网页配置精确 Origin:
|
||
|
||
```ts
|
||
openWebView({
|
||
source: { uri: 'https://verify.example.com/start' },
|
||
permissions: {
|
||
allowedOrigins: ['https://verify.example.com'],
|
||
camera: true,
|
||
microphone: true,
|
||
},
|
||
})
|
||
```
|
||
|
||
Android Manifest 示例:
|
||
|
||
```xml
|
||
<uses-permission android:name="android.permission.CAMERA" />
|
||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||
```
|
||
|
||
Origin 必须包含协议和主机,跨域后的页面不会继承原页面授权。宿主需要自定义决策时:
|
||
|
||
```ts
|
||
permissions: {
|
||
allowedOrigins: ['https://verify.example.com'],
|
||
camera: true,
|
||
microphone: true,
|
||
onRequest(request) {
|
||
if (canUseMedia(request.origin)) request.grant()
|
||
else request.deny()
|
||
},
|
||
}
|
||
```
|
||
|
||
## 文件下载
|
||
|
||
```ts
|
||
openWebView({
|
||
source: { uri: 'https://example.com/files' },
|
||
downloads: {
|
||
auto: true,
|
||
destination: 'publicDownloads',
|
||
conflict: 'rename',
|
||
onProgress(progress) {
|
||
console.log(progress.percentage)
|
||
},
|
||
onComplete(result) {
|
||
console.log(result.filePath)
|
||
},
|
||
onError(url, error) {
|
||
console.warn(url, error)
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
默认保存到应用沙盒。保存到系统 Downloads 只在目标平台支持时生效。
|
||
|
||
## 宿主 Bridge
|
||
|
||
宿主与 H5 自行约定消息协议,通过 `bridge` 注入:
|
||
|
||
```ts
|
||
openWebView({
|
||
source: { uri: 'https://example.com' },
|
||
bridge: {
|
||
async onMessage(raw, context) {
|
||
const message = JSON.parse(raw)
|
||
|
||
if (message.method === 'close') {
|
||
context.close('h5_close', message.data)
|
||
return true
|
||
}
|
||
|
||
if (message.method === 'setTitle') {
|
||
context.updateNavigation({
|
||
title: { text: String(message.title ?? '') },
|
||
})
|
||
return true
|
||
}
|
||
|
||
return false
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
XWebView 不规定方法名、App ID、厂商、用户或业务参数。协议校验、权限和业务结果由宿主
|
||
自己的 Bridge 负责。
|
||
|
||
## 内嵌 WebView
|
||
|
||
不需要独立页面和内置导航栏时:
|
||
|
||
```tsx
|
||
import { XWebViewView } from '@xuqm/rn-xwebview'
|
||
|
||
<XWebViewView
|
||
config={{
|
||
source: { uri: 'https://example.com' },
|
||
}}
|
||
/>
|
||
```
|
||
|
||
内嵌模式由所在页面负责尺寸、导航和关闭交互。
|