Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React to PDF: 좌충우돌 PDF 생성 기능 개발기

kakao
November 01, 2024

React to PDF: 좌충우돌 PDF 생성 기능 개발기

#frontend

리액트 컴포넌트를 PDF 파일로 생성할 수 있게 하는 기능을 개발했습니다. 파일 생성 시 페이지 단위로 픽셀을 맞추기 위해 다양한 스타일링 기술을 활용했으며, 그 과정에서 겪은 어려움과 해결 방법을 공유합니다.

발표자 : zero.choi
카카오헬스케어에서 파스타 서비스의 프론트엔드 개발을 담당하고 있습니다.

kakao

November 01, 2024
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. const MyDocument = () => ( <Document> <Page size=“A4" style={styles.page}>

    <View style={styles.section}> <Text>Hello, if(kakao AI)!</Text> </View> </Page> </Document> )
  2. const MyDocument = () => ( <Document> <Page size="A4" style={styles.page}>

    <View style={styles.section}> <Text>Section #1</Text> </View> </Page> </Document> ) ੉઺੘সࠗ׸ పझ౟ࠂ੟ࢿૐо ۄ੉࠳۞ܻ੄ઓࢿૐо j
  3. QBEEJOHਵ۽೧Ѿೞӝ // Before: ౠ੿ elementܳ ׮਺ ಕ੉૑۽ ֈӝח padding div

    ࢤࢿ var pad = createElement('div', {style: { display: 'block', height: pxPageHeight - (clientRect.top % pxPageHeight) + 'px' }}); el.parentNode.insertBefore(pad, el);
  4. <Provider value={{ isCreatePdfMode: true }}> <Container className={'preview-container'}> {contents.map((content) => (

    <Page key={content} > <Headers style={{ visibility: 'hidden' }} /> <Content contentType={content}/> <Footer style={{ visibility: 'hidden' }} /> </Page> ))} </Container> </Provider>
  5. GFUDIৈࠗܳऔѱ୓௼ೡࣻ੓חߑߨ਷হਸө #FGPSF const data = useFetchAGPContent() const allFetched = data.every((num)

    => num.isFetching === false) const [ { data: glucoseStatisticsData }, { data: timeInRangeData }, { data: agpData }, { data: dailyGlucoseData }, { data: deviceIdUsageData }, ] = data useEffect(() => { if (allFetched) { if (glucoseStatisticsData) setGlucoseStatistics(glucoseStatisticsData) if (timeInRangeData) setTimeInRange(timeInRangeData) if (agpData) setAgp(agpData) if (dailyGlucoseData) setDailyGlucose(dailyGlucoseData) if (deviceIdUsageData) setDeviceIdUsage(deviceIdUsageData) } }, [allFetched]) useEffect(() => { if (allSuccess) { setIsSuccess({ ...isSuccess, agp: true }) } }, [allSuccess]) ݽٚஶబஎী୓௼۽૒ࢤࢿ
  6. #FGPSF const data = useFetchAGPContent() const allFetched = data.every((num) =>

    num.isFetching === false) const [ { data: glucoseStatisticsData }, { data: timeInRangeData }, { data: agpData }, { data: dailyGlucoseData }, { data: deviceIdUsageData }, ] = data useEffect(() => { if (allFetched) { if (glucoseStatisticsData) setGlucoseStatistics(glucoseStatisticsData) if (timeInRangeData) setTimeInRange(timeInRangeData) if (agpData) setAgp(agpData) if (dailyGlucoseData) setDailyGlucose(dailyGlucoseData) if (deviceIdUsageData) setDeviceIdUsage(deviceIdUsageData) } }, [allFetched]) useEffect(() => { if (allSuccess) { setIsSuccess({ ...isSuccess, agp: true }) } }, [allSuccess]) ݽٚஶబஎী୓௼۽૒ࢤࢿ GFUDIৈࠗܳऔѱ୓௼ೡࣻ੓חߑߨ਷হਸө
  7. import { useIsFetching } from ‘react-query' // അ੤ ݻ ѐ੄

    ௪ܻо fetching ࢚కੋ૑ ഛੋ const isFetching = useIsFetching() GFUDIৈࠗܳऔѱ୓௼ೡࣻ੓חߑߨ਷হਸө
  8. import { useIsFetching } from ‘react-query' const isFetching = useIsFetching()

    useEffect(() => { // isFetching੉ 0੉ ؼ ٸ(ݽٚ fetchо ৮ܐؼ ٸ) प೯ setIsFetchComplete(pdfPreviewContents.length > 0 && isFetching === 0) }, [pdfPreviewContents, isFetching]) GFUDIৈࠗܳऔѱ୓௼ೡࣻ੓חߑߨ਷হਸө
  9. #FGPSF "GUFS const data = useFetchAGPContent() const allFetched = data.every((num)

    => num.isFetching === false) const [ { data: glucoseStatisticsData }, { data: timeInRangeData }, { data: agpData }, { data: dailyGlucoseData }, { data: deviceIdUsageData }, ] = data useEffect(() => { if (allFetched) { if (glucoseStatisticsData) setGlucoseStatistics(glucoseStatisticsData) if (timeInRangeData) setTimeInRange(timeInRangeData) if (agpData) setAgp(agpData) if (dailyGlucoseData) setDailyGlucose(dailyGlucoseData) if (deviceIdUsageData) setDeviceIdUsage(deviceIdUsageData) } }, [allFetched]) useEffect(() => { if (allSuccess) { setIsSuccess({ ...isSuccess, agp: true }) } }, [allSuccess]) const isFetching = useIsFetching() useEffect(() => { // isFetching੉ 0੉ ؼ ٸ(ݽٚ fetchо ৮ܐؼ ٸ) प೯ setIsFetchComplete(pdfPreviewContents.length > 0 && isFetching === 0) }, [pdfPreviewContents, isFetching]) ݽٚஶబஎী୓௼۽૒ࢤࢿ ֎઴੄௏٘۽೧Ѿ GFUDIৈࠗܳऔѱ୓௼ೡࣻ੓חߑߨ਷হਸө
  10. <Grid ... domLayout={‘print’} ... /> <Grid ... domLayout={‘autoHeight’} ... />

    1%'ࢤࢿद Ӓ৻ചݶ 1%'ࢤࢿदী݅ز੘ೞѱೡࣻחহਸө
  11. <Provider value={{ isCreatePdfMode: true }}> <Container className={'preview-container'}> {contents.map((content) => (

    <Page key={content}> <Header/> <Content contentType={content}/> <Footer/> </Page> ))} </Container> </Provider>
  12. // canvasܳ ಕ੉૑ ֫੉ী ݏѱ ࠙ೡೞח ೣࣻ const canvasList =

    await sliceCanvas(fullCanvas, fullHeight, scale) // ࠙ೡػ canvas -> image canvasList.forEach((canvas) => { downloadImage( canvas.toDataURL(`image/${format.toLowerCase()}`), `౵ੌݺ`, ) // ... }) ੉޷૑౵ੌ୊ܻبऔѱ؀਽оמೠ۽૒
  13. 2"