“React 检测到钩子顺序发生了变化”,但顺序似乎被保留了下来

Tyc*_*liz 8 reactjs react-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. useContext                 useContext
3. useEffect                  useEffect
4. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    in ExperienceDetailContainer (created by ConnectFunction)
    in ConnectFunction (created by Route)
    in Route (created by withRouter(Connect(ExperienceDetailContainer)))
    in withRouter(Connect(ExperienceDetailContainer)) (created by ExperiencePage)
Run Code Online (Sandbox Code Playgroud)

使用钩子在这里出现意外错误。根据我的理解,只要以可预测的顺序调用钩子,一切都应该正常工作。查看我的组件告诉我就是这种情况,我无法理解错误的来源。

import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import T from 'prop-types'
import { useWait } from 'react-wait'
import { connect } from 'react-redux'
import api from 'services/api'
import {
  formattedDateWithoutTime,
  getTimesForCurrentDate
} from 'utils/helpers'
import { fromResource, fromUserSelections, fromSocial } from 'store/selectors'
import {
  resourceDetailReadRequest,
  changeExperienceDate,
  changeExperienceTime,
  updateExperience,
} from 'store/actions'
import { ExperienceDetail, Spinner } from 'components'

const ExperienceDetailContainer = ({
  detail,
  readDetail,
  experienceDate,
  changeExperienceDate,
  experienceTime,
  changeExperienceTime,
  adults,
  kids,
  updateExperience,
  user,
  ...props
}) => {
  const [error, setError] = useState('')
  const { startWaiting, endWaiting } = useWait()

  useEffect(() => {
    readDetail(apiRequestParams)
    getExperienceAvailabilities()
  }, [])

  const experienceId = props.match.params.experienceid
  const cityId = props.match.params.cityid

  const apiRequestParams = {
    endpoint: "GetExperienceInfo",
    experienceid: experienceId,
    adults,
    kids
  }

  const availableStartTimes = getTimesForCurrentDate(experienceDate, detail.availabilities)

  const getExperienceAvailabilities = () => {
    api.post('/GetExperienceAvailabilities', {
      experienceid: experienceId
    })
    .then(result => {
      const availabilities = result.d
      updateExperience(availabilities)
    })
    .catch(err => {
      console.error(err)
    })
  }

  if (!detail.experienceid) {
    return <Spinner />
  }

  return <ExperienceDetail
    loading={!detail.experienceid}
    {...{
      detail,
      error,
      experienceId,
      availableStartTimes,
      changeExperienceDate,
      experienceDate,
      changeExperienceTime,
      experienceTime,
      getQuoteBooking,
  }}/>
}

ExperienceDetailContainer.propTypes = {
  detail: T.object.isRequired,
  changeExperienceDate: T.func.isRequired,
  experienceDate: T.instanceOf(Date),
  changeExperienceTime: T.func.isRequired,
  experienceTime: T.string,
  adults: T.number.isRequired,
  kids: T.number.isRequired,
}

const mapStateToProps = state => ({
  user: fromSocial.getUser(state),
  detail: fromResource.getDetail(state, 'experience'),
  experienceDate: fromUserSelections.getDate(state),
  experienceTime: fromUserSelections.getTime(state),
  adults: fromUserSelections.getAdults(state),
  kids: fromUserSelections.getAdults(state),
})

const mapDispatchToProps = (dispatch) => ({
  readDetail: (apiRequestParams) => dispatch(resourceDetailReadRequest('experience', { apiRequestParams })),
  changeExperienceDate: (experienceDate) => dispatch(changeExperienceDate(experienceDate)),
  changeExperienceTime: (experienceTime) => dispatch(changeExperienceTime(experienceTime)),
  updateExperience: (availabilities) => dispatch(updateExperience('experience', availabilities))
})

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ExperienceDetailContainer))
Run Code Online (Sandbox Code Playgroud)

可以看出,我使用了 3 个钩子:useState、useWait、useEffect。它们都在顶级调用,而不是在条件或任何会破坏顺序的东西中调用。

我在这里有什么根本性的误解吗?

小智 0

您对钩子用法的理解是正确的,最可能的问题是您的getTimesForCurrentDate函数有条件地使用useState钩子。

React 的消息与您对钩子的使用相匹配 -useWait内部使用useContext并且 React 能够仅列出自己的钩子,这就是为什么有useState, useContext(不是useWait), useEffect。该函数是注册后在渲染中执行的唯一函数useEffect

PS:从技术上讲,如果您在 props.match.params.cityid、...、键之一中有属性 getter 并在那里有条件地使用钩子,也可能会发生这种情况useState,但这感觉真的很糟糕;-)。