You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React循环调用辅助函数致页面崩溃 Hook报错排查

问题定位

你遇到的两个报错本质都是违反React Hooks的核心调用规则:

  • React要求所有Hook只能在函数组件/自定义Hook的顶层作用域调用,不能在循环、条件分支、普通工具函数、useEffect/useMemo等内置Hook的回调函数里调用
  • 你写的普通工具函数getVitalsFromObs内部直接调用了useConfig()这个Hook,本身就不符合Hook调用要求:
    1. 最初你在组件渲染阶段的循环里调用这个工具函数,每次渲染循环执行的次数不固定,导致React记录的Hook调用顺序错乱,触发「Rendered fewer hooks than expected」报错
    2. 后续你把调用挪到useEffect回调里,此时执行上下文不属于组件顶层,直接触发「Invalid hook call」报错
  • 额外问题:你最初写的遍历逻辑直接放在组件渲染阶段、还在遍历里反复调用setX,本身就会触发无限重渲染;而且你把[encounters]写在forEach的第二个参数位置,根本不是Hook的依赖数组,属于语法书写错误。
修复方案

1. 改造普通辅助函数

普通工具函数内部不要调用任何Hook,把函数依赖的config通过入参传入,保持纯逻辑属性:

import { formatTime, parseDate } from '@openmrs/esm-framework';
import type { ConfigObject, Observation, PatientVitals, Encounter } from './types';

// 普通工具函数只处理纯逻辑,依赖值通过参数传入
export const getVitalsFromObs = (enc: Encounter, config: ConfigObject): Array<PatientVitals> => {
  const vitals: Array<PatientVitals> = [];

  enc.obs?.forEach((obs: Observation) => {
    const providerInfo = enc.encounterProviders.length 
      ? {
          name: enc.encounterProviders[0].provider.person.display,
          role: enc.encounterProviders[0].encounterRole.display,
        }
      : { name: '', role: '' };
    const obsTime = formatTime(parseDate(obs.obsDatetime));

    switch(obs.concept?.uuid) {
      case config.concepts.pulseUuid:
        vitals.push({ pulse: obs.value, provider: providerInfo, time: obsTime });
        break;
      case config.concepts.oxygenSaturationUuid:
        vitals.push({ oxygenSaturation: obs.value });
        break;
      case config.concepts.respiratoryRateUuid:
        vitals.push({ respiratoryRate: obs.value });
        break;
      case config.concepts.temperatureUuid:
        vitals.push({ temperature: obs.value });
        break;
      case config.concepts.systolicBloodPressureUuid:
        vitals.push({ systolic: obs.value });
        break;
      case config.concepts.diastolicBloodPressureUuid:
        vitals.push({ diastolic: obs.value });
        break;
      case config.concepts.weightUuid:
        vitals.push({ weight: obs.value });
        break;
      case config.concepts.heightUuid:
        vitals.push({ height: obs.value });
        break;
    }
  });
  return vitals;
};

2. 修正组件逻辑

在组件顶层合法调用useConfig拿到配置,派生的生命体征数据直接用useMemo缓存计算即可,不需要额外用useState存储+反复setState触发重渲染:

import { useMemo, useState } from 'react';
import { useConfig, useTranslation } from '@openmrs/esm-framework';
import { Tabs, Tab } from '@carbon/react';
import { getVitalsFromObs } from '../../helpers/vitals-helper';
import Vitals from './vitals.component';
import styles from './past-visit-summary.scss';

const PastVisitSummary: React.FC<PastVisitSummaryProps> = ({ encounters, patientUuid }) => {
  const { t } = useTranslation();
  const config = useConfig() as ConfigObject;
  const [selectedTabIndex, setSelectedTabIndex] = useState(0);
  const isTablet = useLayoutType() === 'tablet';

  // 直接用useMemo计算派生数据,依赖变化时自动重算
  const allVitals = useMemo(() => {
    const vitalsList: Array<PatientVitals> = [];
    encounters?.forEach((encounterGroup) => {
      encounterGroup?.encounters?.forEach((encounter: Encounter) => {
        vitalsList.push(...getVitalsFromObs(encounter, config));
      });
    });
    return vitalsList;
  }, [encounters, config]);

  return (
    <div className={styles.wrapper}>
      <Tabs className={`${styles.verticalTabs} ${isTablet ? styles.tabletTabs : styles.desktopTabs}`}>
        <Tab
          className={`${styles.tab} ${styles.bodyLong01} ${selectedTabIndex === 0 && styles.selectedTab}`}
          id="vitals-tab"
          onClick={() => setSelectedTabIndex(0)}
          label={t('vitals', 'Vitals')}>
          <Vitals vitals={allVitals} />
        </Tab>
      </Tabs>
    </div> 
  )         
}
注意事项
  • 如果确实需要封装带Hook调用的可复用逻辑,请封装为自定义Hook,函数名以use开头(比如useGetVitalsFromObs),内部可以调用其他Hook,同样只在组件顶层调用
  • 能通过props/state直接计算得到的派生数据,不要额外存到useState里反复set,优先用useMemo做缓存,避免不必要的重渲染和状态同步问题

内容的提问来源于stack exchange,提问作者CKW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:30:56