Expo React Native中API获取PDF的无原生依赖渲染方案求助
Expo环境下React Native渲染PDF的可行方案
问题背景
开发React Native应用时需展示API获取的PDF,使用WebView无法正常工作,且受Expo Go限制不能使用原生依赖。
当前代码
import React, { useEffect, useState } from "react"; import { View, Text, StyleSheet } from "react-native"; import { WebView } from "react-native-webview"; import { IPV4_ADDRESS } from "../views/SiteHome"; export const NodeContent = ({ route }) => { const { id, ticket } = route.params; const [pdfUrl, setPdfUrl] = useState(""); const fetchContentNode = async (id, ticket) => { const myheaders = { method: "GET", headers: { Authorization: "Basic " + ticket, }, }; const response = await fetch( `http://${IPV4_ADDRESS}:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/${id}/content`, myheaders ); const pdfUrl = response.url; console.log(pdfUrl) return pdfUrl; }; useEffect(() => { const getPdfUrl = async () => { const url = await fetchContentNode(id, ticket); setPdfUrl(url); }; getPdfUrl(); }, [id, ticket]); return ( <View style={styles.container}> {pdfUrl ? ( <WebView source={{ uri: pdfUrl }} /> ) : ( <Text>Loading PDF...</Text> )} </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", }, });
解决方案与思路
1. 修复WebView授权问题(最直接)
当前代码的核心问题是:response.url返回的地址没有携带Authorization头,WebView请求时会被Alfresco拒绝访问。解决方式是将PDF内容转为base64格式的Data URI,让WebView直接加载本地数据:
修改fetchContentNode函数:
const fetchContentNode = async (id, ticket) => { const response = await fetch( `http://${IPV4_ADDRESS}:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/${id}/content`, { method: "GET", headers: { Authorization: "Basic " + ticket, }, } ); const blob = await response.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); // 生成data:application/pdf;base64,...格式的URI reader.readAsDataURL(blob); }); };
修改后WebView可以直接加载pdfUrl,无需额外授权,且完全兼容Expo Go。
2. 利用Expo文件系统+WebView/系统浏览器
将PDF下载到本地缓存目录,再通过WebView或系统浏览器加载,适合大文件场景:
首先安装Expo官方库(均兼容Expo Go):
npx expo install expo-file-system
修改代码逻辑:
import * as FileSystem from 'expo-file-system'; const fetchContentNode = async (id, ticket) => { const response = await fetch( `http://${IPV4_ADDRESS}:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/${id}/content`, { headers: { Authorization: "Basic " + ticket } } ); const blob = await response.blob(); const fileUri = `${FileSystem.cacheDirectory}document.pdf`; // 将Blob写入本地文件 const fileWriter = FileSystem.createWriteStream(fileUri); await fileWriter.writeAsync(await blob.arrayBuffer()); await fileWriter.closeAsync(); return fileUri; };
之后可以选择:
- 用WebView加载本地文件:
<WebView source={{ uri: pdfUrl }} /> - 调用系统浏览器打开(需额外安装
expo-web-browser):import * as WebBrowser from 'expo-web-browser'; useEffect(() => { if (pdfUrl) WebBrowser.openBrowserAsync(pdfUrl); }, [pdfUrl]);
3. 基于PDF.js的纯JS渲染(自定义性最强)
使用Mozilla的PDF.js库在WebView中实现自定义PDF渲染,完全无需原生依赖:
- 在项目中创建本地HTML文件
pdf-viewer.html:
<!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script> <style> body, #pdf-container { margin: 0; width: 100%; height: 100vh; } canvas { display: block; width: 100%; margin: 10px 0; } </style> </head> <body> <div id="pdf-container"></div> <script> window.addEventListener('message', async (event) => { // 解析base64格式的PDF数据 const pdfBase64 = event.data.split(',')[1]; const pdfData = atob(pdfBase64); // 加载并渲染PDF const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; const container = document.getElementById('pdf-container'); for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); const viewport = page.getViewport({ scale: 1.2 }); const canvas = document.createElement('canvas'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise; container.appendChild(canvas); } }); // 通知RN页面已加载完成 window.postMessage('ready'); </script> </body> </html>
- 在RN组件中引入WebView并传递PDF数据:
import React, { useEffect, useState, useRef } from "react"; import { View, Text, StyleSheet } from "react-native"; import { WebView } from "react-native-webview"; import { IPV4_ADDRESS } from "../views/SiteHome"; export const NodeContent = ({ route }) => { const { id, ticket } = route.params; const [pdfBase64, setPdfBase64] = useState(""); const webViewRef = useRef(null); const fetchContentNode = async (id, ticket) => { const response = await fetch( `http://${IPV4_ADDRESS}:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/${id}/content`, { headers: { Authorization: "Basic " + ticket } } ); const blob = await response.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); }; useEffect(() => { const getPdfData = async () => { const data = await fetchContentNode(id, ticket); setPdfBase64(data); }; getPdfData(); }, [id, ticket]); return ( <View style={styles.container}> {pdfBase64 ? ( <WebView ref={webViewRef} source={require('./pdf-viewer.html')} onMessage={(event) => { // 页面加载完成后发送PDF数据 if (event.data === 'ready') { webViewRef.current.postMessage(pdfBase64); } }} style={{ flex: 1, width: '100%' }} /> ) : ( <Text>Loading PDF...</Text> )} </View> ); }; const styles = StyleSheet.create({ container: { flex: 1 }, });
内容的提问来源于stack exchange,提问作者Benitez Walter David
相关产品推荐
相关产品推荐

