React PDF查看器组件不断重新渲染

14

我在我的项目中使用了一个React PDF viewer。 我使用了一个react mui dialog component,并结合react draggable来拖动它。

import React from "react";
import withStyles from "@material-ui/core/styles/withStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import DialogContent from "@material-ui/core/DialogContent";
import IconButton from "@material-ui/core/IconButton";
import ClearIcon from "@material-ui/icons/Clear";
import Draggable from "react-draggable";
import Paper from "@material-ui/core/Paper";
import Dialog from "@material-ui/core/Dialog";
import PDFViewer from "./PDFViewer";

function PaperComponent({...props}) {
  return (
    <Draggable
    >
      <Paper {...props} />
    </Draggable>
  );
}

const StyledDialog = withStyles({
  root: {
    pointerEvents: "none"
  },
  paper: {
    pointerEvents: "auto"
  },
  scrollPaper: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    marginRight: 20
  }
})(props => <Dialog hideBackdrop {...props} />);

const useStyles = makeStyles({
  dialog: {
    cursor: 'move'
  },
  dialogContent: {
    '&:first-child': {
      padding: 10,
      background: 'white'
    }
  },
  clearIcon: {
    position: 'absolute',
    top: -20,
    right: -20,
    background: 'white',
    zIndex: 1,
    '&:hover': {
      background: 'white'
    }
  },
  paper: {
    overflowY: 'visible',
    maxWidth: 'none',
    maxHeight: 'none',
    width: 550,
    height: 730
  }
});

const PDFModal = (props) => {
  const classes = useStyles();
  const {open, onClose, pdfURL} = props;
  return (
    <StyledDialog
      open={open}
      classes={{root: classes.dialog, paper: classes.paper}}
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog"
    >
      <DialogContent classes={{root: classes.dialogContent}} id="draggable-dialog">
        <IconButton className={classes.clearIcon} aria-label="Clear" onClick={onClose}>
          <ClearIcon/>
        </IconButton>
        <PDFViewer
          url={pdfURL}
        />
      </DialogContent>
    </StyledDialog>
  );
};


export default PDFModal;

这是PDFViewer组件:

import React from 'react';
import { Viewer, SpecialZoomLevel, Worker  } from '@react-pdf-viewer/core';
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';

import '@react-pdf-viewer/core/lib/styles/index.css';
import '@react-pdf-viewer/default-layout/lib/styles/index.css';
import ArrowForward from "@material-ui/icons/ArrowForward";
import ArrowBack from "@material-ui/icons/ArrowBack";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
import './PDFViewer.css';

const PDFViewer = ({url}) => {
  const renderToolbar = (Toolbar) => (
    <Toolbar>
      {
        (slots) => {
          const {
            CurrentPageLabel, CurrentScale, GoToNextPage, GoToPreviousPage, ZoomIn, ZoomOut,
          } = slots;
          return (
            <div
              style={{
                alignItems: 'center',
                display: 'flex',
              }}
            >
              <div style={{ padding: '0px 2px' }}>
                <ZoomOut>
                  {
                    (props) => (
                      <IconButton aria-label="delete" onClick={props.onClick}>
                        <RemoveCircleOutlineIcon />
                      </IconButton>
                    )
                  }
                </ZoomOut>
              </div>
              <div style={{ padding: '0px 2px' }}>
                <CurrentScale>
                  {
                    (props) => (
                      <span>{`${Math.round(props.scale * 100)}%`}</span>
                    )
                  }
                </CurrentScale>
              </div>
              <div style={{ padding: '0px 2px' }}>
                <ZoomIn>
                  {
                    (props) => (
                      <IconButton aria-label="delete" onClick={props.onClick}>
                        <AddCircleOutlineIcon />
                      </IconButton>
                    )
                  }
                </ZoomIn>
              </div>
              <div style={{ padding: '0px 2px', marginLeft: 'auto' }}>
                <GoToPreviousPage>
                  {
                    (props) => (
                      <Button
                        style={{
                          cursor: props.isDisabled ? 'not-allowed' : 'pointer',
                          height: '30px',
                          width: '30px'
                        }}
                        disabled={props.isDisabled}
                        disableElevation
                        disableFocusRipple
                        onClick={props.onClick}
                        variant="outlined">
                        <ArrowBack fontSize="small"/>
                      </Button>
                    )
                  }
                </GoToPreviousPage>
              </div>
              <div style={{ padding: '0px 2px' }}>
                <CurrentPageLabel>
                  {
                    (props) => (
                      <span>{`${props.currentPage + 1} av ${props.numberOfPages}`}</span>
                    )
                  }
                </CurrentPageLabel>
              </div>
              <div style={{ padding: '0px 2px' }}>
                <GoToNextPage>
                  {
                    (props) => (
                      <Button
                        style={{
                          cursor: props.isDisabled ? 'not-allowed' : 'pointer',
                          height: '30px',
                          width: '30px'
                        }}
                        disabled={props.isDisabled}
                        disableElevation
                        disableFocusRipple
                        onClick={props.onClick}
                        variant="outlined">
                        <ArrowForward fontSize="small"/>
                      </Button>
                    )
                  }
                </GoToNextPage>
              </div>
            </div>
          )
        }
      }
    </Toolbar>
  );

  const defaultLayoutPluginInstance = defaultLayoutPlugin({
    renderToolbar,
    sidebarTabs: defaultTabs => [defaultTabs[1]]
  });

  // constantly called
  console.log('entered')
  return (
    <div
      style={{
        height: '100%',
      }}
    >
      <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.5.207/build/pdf.worker.min.js">
        <Viewer
          fileUrl={url}
          defaultScale={SpecialZoomLevel.PageFit}
          plugins={[
            defaultLayoutPluginInstance
          ]}
        />
      </Worker>
    </div>
  );
};

export default PDFViewer;

我可以在控制台中看到PDFViewer被不断调用。我不确定是什么原因导致了这种重新渲染?


2
我建议你尝试将 renderToolbar 函数定义在 PDFViewer 外部,看看是否有所帮助。 - Linda Paiste
你能提供一个 CodeSandbox 吗? - Mohamed Ramrami
@Macro 我在CodeSandbox中尝试了相同的操作:https://codesandbox.io/s/determined-stonebraker-h7emw?file=/src/PDFViewer.js,但我发现它并没有频繁地调用`console.log(entered)`。(顺便说一句 - 我已经将那个codesandbox中的console.log(entered)更改为console.error(entered)) - Nagaraj Tantri
1个回答

2
当你有一个新的fileUrl传递给PDFModal时,重新渲染是不是很有意义?以下序列应该是应用程序执行的方式。
  1. PDFModalPDFViewer和其他相关组件初始化
  2. 当文件被拖入PaperComponent上下文时,上层组件会处理它并将pdfURL作为props传递
const PDFModal = (props) => {
    const { ......., pdfURL } = props;
    
    //...skipped code    
  
    return (
       <StyledDialog
            PaperComponent={PaperComponent}
       >
           //...skipped code
           <PDFViewer
              url={pdfURL}
           />
       </StyledDialog> 
    );
};

  1. PDFViewer 因为有新的属性而更新。
const PDFViewer = ({ url }) => {
   //...skipped code
   return (
       //...skipped code
       <Viewer
          fileUrl={url}
       />
   );
}

我同意@LindaPaiste所说的,放置Toolbar可能是一个选项,因为它不使用传递的url props。关于重新渲染的问题,我建议可以使用useCallback来包装整个PDFViewer组件。只有在url更改时才更新组件。

这个链接提供了一些关于何时使用useCallback的见解,可以作为参考。

const PDFViewer = useCallback(
    ({ url }) => {
        //...skipped code
        return (
            //...skipped code
            <Viewer
                fileUrl={url}
            />
        )
}, [url])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接