大型 base64 图像在 React 中产生"Maximum call stack size exceeded"



我将网页内容存储为数据库中的byte[]徽标和html主体。当我点击加载特定类别组时;超过了最大调用堆栈大小";如果徽标图像太大(在这种情况下>200kB(。但是,如果我在html主体中存储与base64标记的图像相同的图像,它不会失败。我找了又找,但找不到对这种行为的解释。我寻找其他答案。我改变了存储图像的方式。我测试了不同的尺寸。我希望徽标无论大小都能异步加载。我是React的新手;任何指导都将不胜感激。

这是MemberBenefit对象:

export default interface MemberBenefit {
Id: string;
Name: string;
CategoryId: string;
Logo: ImageFile;
Detail: string;
}
export interface MemberBenefitCategory{
id: string;
Name: string;
ParentId: string;
}
export interface ImageFile{
data: number[];
contentType: string;
fileName: string;
}

此处显示徽标和正文的代码:

<Accordion.Collapse eventKey={benefit.Id.toString()}>
<Card.Body>
{benefit.Logo && 
<>
<img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
</>
}
<section 
className="not-found-controller"
dangerouslySetInnerHTML={{ __html: benefit.Detail }}
/>
</Card.Body>
</Accordion.Collapse>

以下是整个TSX文件以供参考。我确信我在很多地方效率很低,所以我很感激所有建设性的反馈。

import React, { useEffect, useState } from "react"
import {Container, Row, Col, Breadcrumb, Button, Card, Accordion} from "react-bootstrap"
import {Redirect, Link} from "react-router-dom"
import ClientApi from "../../api/ClientApi"
import {ContainsAuthExpiredError} from "../../models/ErrorDetail"
import BenefitGroupPicker from "../../views/benefits/BenefitGroupPicker"
import PageHeading from "../../views/PageHeading"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import MemberBenefit, { MemberBenefitCategory } from "../../models/MemberBenefit"
import { faBriefcase, faCar, faCarrot, faCircle, faDollarSign, faFaceGrinWide, faHeartPulse, faHouseChimney, faPlaneUp, faSailboat, faSquare, faWrench } from "@fortawesome/free-solid-svg-icons"

const Index: React.FC = () => {
const {errors, payload} = ClientApi.Account.UseAccountOverview()    
const accountOverview = payload
const redirectToDisabled = accountOverview?.MembershipSummary.IsDelinquent ?? false 

const [categories, set_categories] = useState<MemberBenefitCategory[] >([]);    
const [selectedParentCategoryIndex, set_selectedParentCategoryIndex] = useState<number | null>(null);
const [loadedMemberBenefits, set_loadedMemberBenefits] = useState<MemberBenefit[] | null>(null);
useEffect(() => {
let mounted = true
const fetchData = async () => {
var [_getCategories, _error] = await ClientApi.Benefits.GetMemberBenefitCategories();           
if (mounted) {
if (_error.length > 0) {
//set_benefitsLoadingErrors(allErrors)
} else {
set_categories(_getCategories ?? [])
}
}
}
fetchData()
return () => {
mounted = false
}
}, [])
const LoadParent = async (parentCategoryId:string) =>{
var [_getMemberBenefits, _error] = await ClientApi.Benefits.GetMemberBenefits(parentCategoryId ?? 0);
set_loadedMemberBenefits(_getMemberBenefits)
set_selectedParentCategoryIndex(categories.findIndex(category=> category.id == parentCategoryId))
}

// maybe fold these into 1 redirection checker function?
if (ContainsAuthExpiredError(errors)) {
return <Redirect to='/login' />
}
if (redirectToDisabled) {
return <Redirect to='/memberBenefits/Disabled' />
}
if (accountOverview && accountOverview.MembershipSummary.IsTerminated) {
return <Redirect to='/myMembership/PayDues' />
}
return (
<div className='component_BenefitsIndex'>
<PageHeading>
Member <b>Benefits</b>
</PageHeading>

<Container className='pageContent'>
<div className="d-lg-none d-xl-none ">
{selectedParentCategoryIndex == null  &&
<>
<div id="benefitsPicker" style={{marginTop:"15px"}}>
<Row style={{textAlign:"center",lineHeight:"48px"}}>
<Col>
<button onClick={() => LoadParent("5")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}}>
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faWrench} size='2x'/>                        
</span>
<div>Equipment</div>
</button>                                
</Col>
<Col>
<button onClick={() => LoadParent("1")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                        
<FontAwesomeIcon style={{color:"white", fontSize:"30px", top:"-15px", left:"-15px"}} icon={faSailboat}/>                            
<FontAwesomeIcon style={{color:"#FF671E", fontSize:"25px",top:"10px", left:"10px"}}  icon={faSquare}  />                                        
<FontAwesomeIcon style={{color:"#FF671E", fontSize:"35px", top:"10px", left:"10px"}} icon={faCar} />                                                    
<FontAwesomeIcon style={{color:"#FF671E", width:"100px", height:"10px", top:"15px", left:"-7px"}}  icon={faSquare}  />
<FontAwesomeIcon style={{color:"white", fontSize:"30px", top:"10px", left:"10px"}} icon={faCar} />
</span>
<div>Auto/Boat</div>
</button>           
</Col>
<Col>
<button onClick={() => LoadParent("2")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faPlaneUp} size='2x'/>                       
</span>
<div>Travel</div>
</button>           
</Col>
</Row>
<Row style={{textAlign:"center",lineHeight:"48px"}}>
<Col>
<button onClick={() => LoadParent("6")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faDollarSign} size='2x'/>                        
</span>
<div>Financial</div>    
</button>       
</Col>
<Col>
<button onClick={() => LoadParent("3")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faHouseChimney} size='2x'/>                      
</span>
<div>Home/Farm</div>    
</button>       
</Col>
<Col>
<button onClick={() => LoadParent("7")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faHeartPulse} size='2x'/>                        
</span>
<div>Health</div>   
</button>       
</Col>
</Row>
<Row  style={{textAlign:"center",lineHeight:"48px"}}>
<Col>
<button onClick={() => LoadParent("4")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faFaceGrinWide} size='2x'/>                      
</span>
<div style={{marginTop: "11px",lineHeight: "25px"}}>Attractions/<br/>Sports</div>       
</button>   
</Col>
<Col>
<button onClick={() => LoadParent("8")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faBriefcase} size='2x'/>                     
</span>
<div>Career</div>
</button>           
</Col>
<Col>
<button onClick={() => LoadParent("9")}>
<span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
<FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
<FontAwesomeIcon style={{color:"white"}} icon={faCarrot} size='2x'/>                        
</span>
<div>FAMA</div>     
</button>   
</Col>
</Row>
</div>
</>
}
{selectedParentCategoryIndex != null &&
<>
<button onClick={()=>set_selectedParentCategoryIndex(null)}>back</button>
<div className='blockNavContent'>
<h2>{categories[selectedParentCategoryIndex].Name}</h2>

{
categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
<Accordion key={category.id+"sub"}>
<Card>
<Card.Header>
{/* This was Accordion.Toggle in old version */}
<Accordion.Toggle as={Button} variant="link" eventKey={category.id.toString()}>
{category.Name} 
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey={category.id.toString()}>
<Card.Body>
{
loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == category.id.toString()).map(benefit=>                                           
  <Accordion key={benefit.Id +"subbenefit"}>
      <Card >
          <Card.Header>
              {/* This was Accordion.Toggle in old version */}
              <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
                  {benefit.Name}
              </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey={benefit.Id.toString()}>
              <Card.Body>
                  {benefit.Logo && 
                      <>
                          <img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                      </>
                  }
                  <section 
                      className="not-found-controller"
                      dangerouslySetInnerHTML={{ __html: benefit.Detail }}
                  />
              </Card.Body>
          </Accordion.Collapse>
      </Card>
  </Accordion>
)
}
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
{
loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == categories[selectedParentCategoryIndex].id.toString()).map(benefit=>                                            
<Accordion key={benefit.Id +"sub"}>
<Card >
<Card.Header>
{/* This was Accordion.Toggle in old version */}
<Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
{benefit.Name}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey={benefit.Id.toString()}>
<Card.Body>

{benefit.Logo && 
  <>
      <img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
  </>
}
<section
  className="not-found-controller"
  dangerouslySetInnerHTML={{ __html: benefit.Detail }}
/>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
</div>
</>
}
</div>
<Row className="d-none d-lg-block">
<Col>
<div className="blockNavGrid">
<div className="blockNavsList">
{
categories.filter(category=>{return category.ParentId == ""}).map(category=>

<div key={category.id}>
<Button onClick={() => LoadParent(category.id)} >
{category.Name}
</Button>
</div>

)
}
</div>
{selectedParentCategoryIndex == null &&
<>  
<div className='blockNavContent'>
<div style={{marginBottom: "48px"}}>
<img
src='https://apps.floridafarmbureau.com/_cdn/img/MembershipMeansMoreHeader.svg'
alt='Membership Means More'
/>
</div>
<p>
Membership with Florida Farm Bureau means that you are part of a
growing community of member families in Florida. Members, just
like you, advocate for agriculture and grow as better leaders in
our state through their membership. As a thank you for your
membership, you are provided with access to over 30 companies to
provide benefits and discounts.
</p>
<p>Click a section for more information. </p>
</div>
</>
}
{selectedParentCategoryIndex != null &&
<>  
<div className='blockNavContent'>
<h2>{categories[selectedParentCategoryIndex].Name}</h2>

{
categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
<Accordion key={category.id+"sub"}>
<Card>
<Card.Header>
{/* This was Accordion.Toggle in old version */}
<Accordion.Toggle as={Button} variant="link" eventKey={category.id.toString()}>
{category.Name} 
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey={category.id.toString()}>
<Card.Body>
{
loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == category.id.toString()).map(benefit=>                                           
<Accordion key={benefit.Id +"subbenefit"}>
  <Card >
      <Card.Header>
          {/* This was Accordion.Toggle in old version */}
          <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
              {benefit.Name}
          </Accordion.Toggle>
      </Card.Header>
      <Accordion.Collapse eventKey={benefit.Id.toString()}>
          <Card.Body>
              {benefit.Logo && 
                  <>
                      <img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                  </>
              }
              <section 
                  className="not-found-controller"
                  dangerouslySetInnerHTML={{ __html: benefit.Detail }}
              />
          </Card.Body>
      </Accordion.Collapse>
  </Card>
</Accordion>
)
}
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
{
loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == categories[selectedParentCategoryIndex].id.toString()).map(benefit=>                                            
<Accordion key={benefit.Id +"sub"}>
<Card >
<Card.Header>
{/* This was Accordion.Toggle in old version */}
<Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
{benefit.Name}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey={benefit.Id.toString()}>
<Card.Body>

{benefit.Logo && 
<>
  <img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
</>
}
<section
className="not-found-controller"
dangerouslySetInnerHTML={{ __html: benefit.Detail }}
/>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
</div>
</>
}
</div>
</Col>
</Row>
</Container>
</div>
)
}
export default Index

错误屏幕

错误文本:

RangeError: Maximum call stack size exceeded
(anonymous function)
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:233
230 | 
231 | {benefit.Logo && 
232 |     <>
> 233 |         <img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
| ^  234 |    </>
235 | }
236 | <section

Index
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:175
172 | 
173 | {
174 |     categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
> 175 |         <Accordion key={category.id+"sub"}>
| ^  176 |            <Card>
177 |                 <Card.Header>
178 |                     {/* This was Accordion.Toggle in old version */}

▶ 18 stack frames were collapsed.
LoadParent
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:48
45 | const LoadParent = async (parentCategoryId:string) =>{
46 |  var [_getMemberBenefits, _error] = await MyFfbfClientApi.Benefits.GetMemberBenefits(parentCategoryId ?? 0);
47 |  set_loadedMemberBenefits(_getMemberBenefits)
> 48 |  set_selectedParentCategoryIndex(categories.findIndex(category=> category.id == parentCategoryId))
| ^  49 | }
50 | 
51 | // maybe fold these into 1 redirection checker function?

我认为问题是:String.fromCharCode.apply它被调用用于benefit.Logo.data内联中的每个字节/数字,并且在特定大小下调用堆栈超过。

apply((方法调用具有给定this的指定函数价值

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

但要注意:通过这种方式使用apply,您将面临超过JavaScript引擎的参数长度限制。应用参数过多的函数(即超过十个数千个参数(在不同的引擎中有所不同。(JavaScriptCore引擎的硬编码参数限制为65536。(

所以内部看起来是这样的:

// `null` will be replaced with the global object
btoa(window.String.fromCharCode(benefit.Logo.data[0]) + window.String.fromCharCode(benefit.Logo.data[1]) + ...)

也许你可以将图像存储为原始二进制文件,比如Image()ArrayBuffer()或类似文件,然后调用toString("base64")

伪代码:

export interface ImageFile{
data: ArrayBuffer;
contentType: string;
fileName: string;
}
<img style={{width:"100%"}} src={"data: "+benefit.Logo.contentType+"; base64," + benefit.Logo.data.toString("base64") }>

参考文献:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

https://developer.mozilla.org/en-US/docs/Web/API/Blob

https://developer.mozilla.org/en-US/docs/Glossary/Base64

相关内容

最新更新