React.js应用调用documents.get API展示Google Docs内容实现方法咨询
React应用渲染Google Docs文档内容实现方案
方案选型
根据场景可以二选一:
- 无自定义需求、文档公开可访问:直接用iframe嵌入官方预览地址即可,不需要调用API,代码示例:
<iframe src={https://docs.google.com/document/d/${你的文档ID}/preview} width="100%" height="800px" frameBorder="0"></iframe>,缺点是无法自定义样式、无法适配私有文档鉴权场景。 - 需要自定义渲染、控制权限/样式:走Google Docs API
documents.get接口自行解析渲染,灵活度最高。
接口响应解析核心实现
Google Docs documents.get 返回的内容结构是分层的块级结构,核心内容存在body.content数组中,每个元素对应一个文档块(段落、表格、图片、分页符等),块内嵌套行内内容(文本片段、内联样式、内联图片),递归遍历解析即可映射为React组件。
最简可运行的解析渲染代码如下:
// 解析文本内联样式 const parseTextStyle = (textStyle) => { const domStyle = {}; if (textStyle.bold) domStyle.fontWeight = 700; if (textStyle.italic) domStyle.fontStyle = 'italic'; if (textStyle.underline) domStyle.textDecoration = 'underline'; if (textStyle.strikethrough) { domStyle.textDecoration = domStyle.textDecoration ? `${domStyle.textDecoration} line-through` : 'line-through'; } if (textStyle.fontSize?.magnitude) { domStyle.fontSize = textStyle.fontSize.unit === 'PT' ? `${textStyle.fontSize.magnitude}px` : textStyle.fontSize.magnitude; } if (textStyle.foregroundColor?.color?.rgbColor) { const rgb = textStyle.foregroundColor.color.rgbColor; domStyle.color = `rgb(${rgb.red * 255}, ${rgb.green * 255}, ${rgb.blue * 255})`; } if (textStyle.link?.url) domStyle.cursor = 'pointer'; return domStyle; }; // 单块内容递归渲染组件 const DocBlockRenderer = ({ block, inlineObjects }) => { // 渲染段落类内容(含正文、标题、列表项) if (block.paragraph) { const { namedStyleType } = block.paragraph.paragraphStyle || {}; // 映射标题层级 const TagMap = { HEADING_1: 'h1', HEADING_2: 'h2', HEADING_3: 'h3', HEADING_4: 'h4', HEADING_5: 'h5', HEADING_6: 'h6', NORMAL_TEXT: 'p' }; const Tag = TagMap[namedStyleType] || 'p'; return ( <Tag style={{ margin: '0.8em 0', lineHeight: 1.6 }}> {block.paragraph.elements.map((el, idx) => { // 渲染普通文本片段 if (el.textRun) { const { content, textStyle } = el.textRun; if (textStyle.link?.url) { return ( <a key={idx} href={textStyle.link.url} style={parseTextStyle(textStyle)}> {content} </a> ); } return ( <span key={idx} style={parseTextStyle(textStyle)}> {content} </span> ); } // 渲染内联图片 if (el.inlineObjectElement) { const objId = el.inlineObjectElement.inlineObjectId; const imgSource = inlineObjects[objId]?.inlineObjectProperties ?.embeddedObject?.imageProperties?.contentUri; return imgSource ? <img key={idx} src={imgSource} alt="" style={{ maxWidth: '100%' }} /> : null; } return null; })} </Tag> ); } // 渲染表格 if (block.table) { return ( <table style={{ borderCollapse: 'collapse', width: '100%', margin: '1em 0' }}> <tbody> {block.table.tableRows.map((row, rIdx) => ( <tr key={rIdx}> {row.tableCells.map((cell, cIdx) => ( <td key={cIdx} style={{ border: '1px solid #e5e7eb', padding: '8px' }}> {cell.content.map((cellBlock, i) => ( <DocBlockRenderer key={i} block={cellBlock} inlineObjects={inlineObjects} /> ))} </td> ))} </tr> ))} </tbody> </table> ); } // 渲染分页/分节符 if (block.pageBreak || block.sectionBreak) return <hr style={{ margin: '2em 0' }} />; return null; }; // 文档渲染入口组件 const GoogleDocViewer = ({ apiResponse }) => { return ( <div className="google-doc-container" style={{ maxWidth: 900, margin: '0 auto' }}> {apiResponse.body.content.map((block, idx) => ( <DocBlockRenderer key={idx} block={block} inlineObjects={apiResponse.inlineObjects} /> ))} </div> ); }; export default GoogleDocViewer;
避坑说明
- 调用
documents.get接口时必须配置fields请求参数,否则返回结果会缺失图片地址、样式等关键信息,推荐配置值:body(content(paragraph(elements(textRun(content,textStyle),inlineObjectElement(inlineObjectId)),paragraphStyle(namedStyleType)),table(tableRows(tableCells(content))))),inlineObjects - 接口返回的图片
contentUri是带临时鉴权的链接,有效期约1小时,如需长期展示文档内图片,需要在拿到接口响应后将图片转存到自有对象存储服务 - 列表、脚注、批注、公式这类特殊格式,只需要在对应块节点中匹配对应字段扩展渲染逻辑即可,整体解析逻辑和段落、表格一致,都是递归遍历块结构
- 如果不想从零实现全量格式解析,可以选用仅做Google Docs JSON转HTML的轻量工具库,避免引入封装过重、依赖复杂的全量viewer包,减少兼容性问题
内容的提问来源于stack exchange,提问作者Batu
相关产品推荐
相关产品推荐

