React useContext + useReducer:防止不必要的重新渲染以优化性能

时间:2021-04-30 11:17:21

标签: javascript reactjs optimization react-hooks

每当我在任何输入字段中输入内容时,我肯定都会遇到性能问题。这个性能问题就像按住一个键 -> 然后它“暂停”输入字段 -> 然后我停止按住键 -> 然后我可以看到输入值。所以基本上它是滞后。 - 或者用技术的话来说:按下按键开始重新渲染所有其他导致输入延迟的子组件和孙组件。

经过一些分析和 console.logs - 看起来问题出在我的表组件上,该组件正在重新渲染每个按键上的每一行。

我的分析器: enter image description here

我有一个名为 Afrapporter 的主容器,其中我的上下文和 reducer 放置在同一路径中:

const KontrolrapportContext = React.createContext()

const reducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_INITIALSTATE': {
      // update state
    }
    case 'HANDLE_CHANGE_TOPSECTION': {
     // update state
    }
    case 'HANDLE_CHANGE_RUBRIKFEJL': {
      // update state
    }
    case 'HANDLE_CHANGE_RUBRIKFEJL_MULTIPLE': {
      // update state
    }
    case 'HANDLE_CHANGE_TOLDRAPPORT': {
      // update state
    default:
      throw new Error(`Unhandled action type: ${action.type}`)
  }
}

const Afrapporter = () => {
    const kontrolrapport = useSelector(selectKontrolrapport)

    const [state, dispatchLocal] = useReducer(reducer, {})

    useEffect(() => {
      dispatchLocal({ type: 'UPDATE_INITIALSTATE', payload: kontrolrapport })
    }, [kontrolrapport])

    const dispatch = useDispatch()

    let { kontrolrapportId } = useParams()

    useEffect(() => {
      dispatch(fetchKontrolrapportById(kontrolrapportId))
    }, [kontrolrapportId, dispatch])

    const value = { state, dispatchLocal }

    return (
      <>
        <KontrolrapportContext.Provider value={value}>
          <Grid container justify='center' alignItems='center' spacing={3}>
            
          <Grid container justify='center' spacing={3}>
            <Grid item sm={12}>
              {state && <TopSection />}
            </Grid>
            <Grid item xs={12}>
              <Typography variant='h5'>Rubrikker</Typography>
              {state && <AfrapporteringTable />}
            </Grid>
          </Grid>
        </KontrolrapportContext.Provider>
      </>
    )
  }
}

function useKontrolrapport() {
  const context = React.useContext(KontrolrapportContext)

  if (context === undefined) {
    throw new Error('useKontrolrapport must be used within a KontrolrapportProvider')
  }
  return context
}

export { Afrapporter, useKontrolrapport }

我的容器有 2 个用户可以与之交互的子组件:TopSectionAfrapporteringTable

  1. TopSection 是一个包含 10 多个输入字段的表单 - 作为组件提取 - 它使用我的上下文来获取值并设置值。
  2. AfrapporteringTable 是一个表格,它显示了我的上下文中的一些数据以及用户可以与之交互的数据。交互式输入类型是选择器。

TopSection 为每个输入元素都有一个组件,例如:

const TopSection = () => {
  return (
    <TableContainer component={Paper}>
      <Table size='small'>
        <TableBody>
          <Referencenummer />
          <Varepostnummer />
          <Journalnummer />
          <Branchekode />
          <ToldmaessigAendringOpkraevning />
          <ToldmaessigAendringTilbagebetaling />
          <FysiskKontrol />
          <AfkrydsForOversendelseAfSagTilFinansielAnalyse />
          <Ansvarsvurdering />
          <Varemodtagernavn />
          <VaremodtagerCvr />
          <AntagetDato />
        </TableBody>
      </Table>
    </TableContainer>
  )
}

export default TopSection

// TopSection childcomponent e.g.:
const Journalnummer = () => {
  const { state, dispatchLocal } = useKontrolrapport()
  const id = 'workzoneJournalnummer'
  const label = 'Journalnummer'

  return (
    <TableRow>
      <TableCell>
        <InputLabel>{label}</InputLabel>
      </TableCell>
      <TableCell>
        <TextField
          id={id}
          type='number'
          style={{ margin: 3 }}
          fullWidth
          margin='normal'
          size='small'
          value={state.workzoneJournalnummer || ''}
          variant='outlined'
          onChange={(event) => dispatchLocal({ type: 'HANDLE_CHANGE_TOPSECTION', payload: event })}
        />
      </TableCell>
    </TableRow>
  )
}

export default Journalnummer

这是我的AfrapporteringTable:`

const Rubrik = {
  RUBRIK_TYPE: 'type',
  ORIGINAL_VAERDI: 'originalVaerdi',
  KORRIGERET_VAERDI: 'korrigeretVaerdi',
  MULIGE_FEJL: 'muligeFejl',
}

const RubrikTyper = {
  PRAEFERENCE_DOK: 'Præferencedok',
  STATISTISK_VAERDI: 'Statistisk værdi',
}

const headers = [
  {
    id: Rubrik.RUBRIK_TYPE,
    label: 'Rubrik',
  },
  {
    id: Rubrik.ORIGINAL_VAERDI,
    label: 'Original værdi',
  },
  {
    id: Rubrik.KORRIGERET_VAERDI,
    label: 'Korrigeret værdi',
  },
  {
    id: Rubrik.MULIGE_FEJL,
    label: 'Mulige fejl',
  },
]

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
    },
  },
};

const useStyles = makeStyles(() => ({
  muligeFejlSelect: {
    minWidth: 300,
    maxWidth: 300,
    height: '2.5em',
  },
  row: {
    height: "40px",
  },
  head: {
    backgroundColor: '#14143c',
    textAlign:'center',
    color: '#ffffff',
    minWidth: '200px',
  },
  cell: {
    textAlign:'center',
  }
}))

const MuligeFejlMultipleValues = ({
  index,
  name,
  options,
}) => {
  const classes = useStyles()
  const { state, dispatchLocal } = useKontrolrapport()
  

  const value = state.rubrikker[index]?.valgteFejl.map((item) => item.fejltekstId)

  return (
    <Select
      className={classes.muligeFejlSelect}
      name={name}
      multiple
      MenuProps={MenuProps}
      value={value}
      onChange={(event) => dispatchLocal({ type: 'HANDLE_CHANGE_RUBRIKFEJL_MULTIPLE', payload: event, index: index})}
      variant='outlined'>
      <MenuItem value=''>
        <em>----</em>
      </MenuItem>

      {options.length > 0 &&
        options.map((option) => (
          <MenuItem key={option.fejltekstId} value={option.fejltekstId}>
            {option.tekst}
          </MenuItem>
        ))}
    </Select>
  )
}

const MuligeFejlSingleValue = ({
  index,
  name,
  options,
}) => {
  const classes = useStyles()
  const { state, dispatchLocal } = useKontrolrapport()
  

  const value = state.rubrikker[index]?.valgteFejl[0]?.fejltekstId

  return (
    <Select
      className={classes.muligeFejlSelect}
      name={name}
      value={value}
      onChange={(event) => dispatchLocal({ type: 'HANDLE_CHANGE_RUBRIKFEJL', payload: event, index: index})}
      variant='outlined'>
      <MenuItem value=''>
        <em>----</em>
      </MenuItem>

      {options.length > 0 &&
        options.map((option) => (
          <MenuItem key={option.fejltekstId} value={option.fejltekstId}>
            {option.tekst}
          </MenuItem>
        ))}
    </Select>
  )
}

const SwitchRubrikComponent = ({
  index,
  rubrikType,
  value,
  header,
}) => {
  const classes = useStyles()
  console.log("switch rendered")
  switch (header.id) {
    case Rubrik.RUBRIK_TYPE: {
      return (
        <TableCell key={header.id}>
          {value}
        </TableCell>
      )
    }
    case Rubrik.ORIGINAL_VAERDI:
    case Rubrik.KORRIGERET_VAERDI: {
      return (
        <TableCell className={classes.cell}>
          <TextField
             key={header.id}
            size='small'
            fullWidth
            value={value}
            variant='outlined'
            InputProps={{
              readOnly: true,
            }}
          />
        </TableCell>
      )
    }
    case Rubrik.MULIGE_FEJL: {
      if (rubrikType === RubrikTyper.PRAEFERENCE_DOK || rubrikType === RubrikTyper.STATISTISK_VAERDI) {
        return (
          <TableCell  className={classes.cell}>
            <MuligeFejlMultipleValues
              key={header.id}
              index={index}
              name={rubrikType}
              options={value}
            />
          </TableCell>
        )
      }
      return (
        <TableCell className={classes.cell}>
          <MuligeFejlSingleValue
            key={header.id}
            index={index}
            name={rubrikType}
            options={value}
          />
        </TableCell>
      )
    }
    default:
      console.error('Could not find any rubrik with the type: ' + header.id)
      return null
  }
}

const Header = () => {
  const classes = useStyles()

  return (
    <TableHead>
      <TableRow>
        {headers.map((header) => (
          <TableCell key={header.id} className={classes.head}>
            {header.label}
          </TableCell>
        ))}
      </TableRow>
    </TableHead>
  )
}

const Body = () => {
  const classes = useStyles()
  const { state } = useKontrolrapport()

  if (Object.keys(state).length) {
    return (
      <TableBody>
        {state.rubrikker.map((rubrik, index) => {
          return (
            <TableRow
              key={rubrik.rubrikId}
              className={classes.row}
              hover
              role='checkbox'
              tabIndex={-1}>
              {headers.map((header) => {
                const value = rubrik[header.id] || ''

                return (
                  <SwitchRubrikComponent
                    key={header.id}
                    index={index}
                    rubrikType={rubrik.type}
                    value={value}
                    header={header}
                  />
                )
              })}
            </TableRow>
          )
        })}
      </TableBody>
    )
  } else {
    return null
  }
}

const AfrapporteringTable = () => {
  return (
    <TableContainer component={Paper}>
      <Table>
        <Header />
        <Body />
      </Table>
    </TableContainer>
  )
}

export default AfrapporteringTable

如您所见,我在深度嵌套的子组件中调用 useKontrolrapport() - 这导致当只有一个组件更改了其值时,其他子组件的不必要重新渲染。如果我理解正确,这是 useContext() 钩子的预期行为。

所以我的问题是:

如何优化我的应用程序,以便用户可以在输入字段中输入内容而不会遇到延迟?

0 个答案:

没有答案