13

我在我的项目中使用React PDF 查看器。我有一个react mui 对话框组件,我使用 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 不断被调用。我不确定是什么原因导致整个时间重新渲染?

4

1 回答 1

2

fileUrl当你有一个新的传递给时重新渲染是否有意义PDFModal?以下顺序应该是应用程序的执行方式。

  1. PDFModal,PDFViewer和其他相关组件 init
  2. 当文件被拖入PaperComponent上下文时,上层组件处理它并pdfURL作为道具传递
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传入的道具。对于重新渲染问题,我建议useCallback可以用来包装整个PDFViewer组件。仅在url更改时更新组件。

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

const PDFViewer = useCallback(
    ({ url }) => {
        //...skipped code
        return (
            //...skipped code
            <Viewer
                fileUrl={url}
            />
        )
}, [url])
于 2021-01-30T01:54:16.720 回答