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

React中工具函数用useContext及组件调用报错问题咨询

问题分析与解决方案

核心问题拆解

  1. 普通函数无法调用React钩子:你最初尝试在普通工具函数calculatePercentage里直接使用useContext,违反了React钩子规则——钩子只能在组件或自定义钩子的顶层调用。
  2. 组件内钩子调用位置错误:修改为自定义钩子后,在map循环里调用useCalculatePercentage,同样违反钩子规则,导致报错。

解决方案一:改造为合规的自定义钩子

将工具函数封装为返回计算逻辑的自定义钩子,在组件顶层调用钩子获取计算函数,再在循环中使用该函数。

1. 修复工具文件 calculatePercentage.js

import { addDays, differenceInCalendarDays } from "date-fns";
import { useContext } from "react";
import { TimelineSettingsContext } from "../TimelineSettingsProvider";

// 自定义钩子:返回一个可复用的计算函数
export function useCalculatePercentage() {
  // 在钩子顶层获取上下文,符合规则
  const { timelineStart, timelineLength, timelineScale } = useContext(TimelineSettingsContext);

  // 返回纯计算逻辑的函数,供组件循环调用
  return (startDate, endDate) => {
    // 修正startDate早于时间线起点的情况
    const adjustedStartDate = differenceInCalendarDays(timelineStart, startDate) < 0
      ? startDate
      : timelineStart;
    
    const startPosition =
      (differenceInCalendarDays(adjustedStartDate, timelineStart) / timelineLength) * timelineScale;
    
    const width =
      (differenceInCalendarDays(addDays(endDate, 1), adjustedStartDate) / timelineLength) * timelineScale;
    
    return { startPosition, width };
  };
}

2. 修复Timeline组件调用逻辑

import { differenceInCalendarDays } from "date-fns";
import React, { useContext } from "react";
import BackgroundColumns from "./BackgroundColumns";
import BookingBar from "./BookingBar";
import { TimelineSettingsContext } from "../../TimelineSettingsProvider";
import { useCalculatePercentage } from "../../lib/calculatePercentage";

const Timeline = () => {
  const { groupedBookings, dates, setSelectedBooking } = useContext(TimelineSettingsContext);
  // 在组件顶层调用自定义钩子,获取计算函数(符合钩子规则)
  const calculatePercentage = useCalculatePercentage();

  function handleBookingSelected(booking) {
    setSelectedBooking(booking.LfdNr);
  }

  return (
    <div className="whitespace-nowrap relative">
      <div className="absolute flex w-full h-full">
        {dates?.map((date, index) => {
          // 调用顶层获取的计算函数,而非钩子
          const { width } = calculatePercentage(new Date(), new Date());
          return (
            <BackgroundColumns
              key={index}
              date={date}
              width={width}
            ></BackgroundColumns>
          );
        })}
      </div>
      <div className="relative flex flex-col z-20 pt-8">
        {groupedBookings.map((category, index) => {
          return (
            <React.Fragment key={index}>
              <div className="h-8" key={`${index}`} />
              {category.bookings.map((booking, idx) => {
                // 同样调用计算函数
                const { startPosition, width } = calculatePercentage(
                  new Date(booking.Beginn_Datum),
                  new Date(booking.Ende_Datum)
                );
                const bookingLength = differenceInCalendarDays(booking.Ende_Datum, booking.Beginn_Datum) + 1;

                return (
                  <BookingBar
                    callbackBarClicked={() => handleBookingSelected(booking)}
                    key={booking.LfdNr}
                    startPosition={startPosition}
                    width={width}
                    index={index}
                    label={`${bookingLength} ${bookingLength > 1 ? "days" : "day"}`}
                  />
                );
              })}
            </React.Fragment>
          );
        })}
      </div>
    </div>
  );
};

export default Timeline;

解决方案二:纯函数传参(无钩子依赖)

如果希望计算逻辑脱离React上下文,可将上下文参数直接传入纯函数,更易测试和复用。

1. 工具文件改造

import { addDays, differenceInCalendarDays } from "date-fns";

// 纯函数:所有依赖通过参数传入,无React依赖
export function calculatePercentage(startDate, endDate, timelineStart, timelineLength, timelineScale) {
  const adjustedStartDate = differenceInCalendarDays(timelineStart, startDate) < 0
    ? startDate
    : timelineStart;
  
  const startPosition =
    (differenceInCalendarDays(adjustedStartDate, timelineStart) / timelineLength) * timelineScale;
  
  const width =
    (differenceInCalendarDays(addDays(endDate, 1), adjustedStartDate) / timelineLength) * timelineScale;
  
  return { startPosition, width };
}

2. 组件调用

// 导入纯函数
import { calculatePercentage } from "../../lib/calculatePercentage";

const Timeline = () => {
  // 从上下文取出所需参数
  const { groupedBookings, dates, setSelectedBooking, timelineStart, timelineLength, timelineScale } = useContext(TimelineSettingsContext);

  // ...其他代码

  // 在循环中调用时传入参数
  const { width } = calculatePercentage(new Date(), new Date(), timelineStart, timelineLength, timelineScale);
}

方案选择建议

  • 若计算逻辑仅在React组件中使用,且频繁依赖上下文,优先用自定义钩子方案。
  • 若需要在非React环境复用计算逻辑,或追求更高的可测试性,选择纯函数传参方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:14:54