You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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渲染,完全无需原生依赖:

  1. 在项目中创建本地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>
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:35:53