Я новичок в реагировании и JS. Я хочу немного провести рефакторинг и очистить свои бесконечные файлы и разделить их на файлы для разных задач. Итак, я создал папку lib и файл с именем CalculPercentage.js, и я хочу использовать там перехватчик useContext. Как этого можно достичь? Нужно ли мне создавать компонент?
это невозможно:
import { addDays, differenceInCalendarDays } from "date-fns";
import { useContext } from "react";
import { TimelineSettingsContext } from "../TimelineSettingsProvider";
// Function to calculate the percentage difference between two dates
export function calculatePercentage(startDate, endDate) {
const { timelineStart, timelineLength, timelineScale } = useContext(
TimelineSettingsContext
);
//wenn startDate vor dem TimelineStart liegt, dass muss der Balken verkürzt werden und startet bei timelineStart
startDate =
differenceInCalendarDays(timelineStart, startDate) < 0
? startDate
: timelineStart;
const startPosition =
(differenceInCalendarDays(startDate, timelineStart) / timelineLength) *
timelineScale;
const width =
(differenceInCalendarDays(addDays(endDate, 1), startDate) /
timelineLength) *
timelineScale;
return { startPosition, width };
}
Теперь, после решения этой ошибки с комментарием ниже. Получение еще одной ошибки от вызывающего компонента:
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
);
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 } = useCalculatePercentage(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}`} // Verwenden Sie eine eindeutige Kombination aus index und idx für den key
/>
{category.bookings.map((booking, idx) => {
const { startPosition, width } = useCalculatePercentage(
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} // Verwenden Sie eine eindeutige Kombination aus index und idx für den key
startPosition = {startPosition}
width = {width}
index = {index}
label = {`${bookingLength} ${
bookingLength > 1 ? "days" : "day"
}`}
/>
);
})}
</React.Fragment>
);
})}
</div>
</div>
);
};
export default Timeline;
Да, как вы заметили, в функциях нельзя использовать хуки. Перехватчики React можно вызывать только внутри функционального компонента или из другого пользовательского перехватчика. Поэтому, имея дело с возвратами, отличными от JSX, вы должны создать еще один крючок, из которого вы сможете вызывать нужные вам хуки. Итак, в этом случае вы, скорее всего, напишете что-то вроде
export function useCalculatePercentage(startDate, endDate) {
const { timelineStart, timelineLength, timelineScale } = useContext(
TimelineSettingsContext,
);
// ...
return { startPosition, width };
}
И тогда у вас есть новый крючок, который вы можете использовать для получения нужного вам процентного значения. И это снова необходимо вызвать на верхнем уровне в функциональном компоненте или другом хуке.
В вашем случае, когда вы используете useCalculatePercentage
, он не будет работать должным образом или будет выдавать предупреждения. Вы должны вызывать перехватчик на верхнем уровне функции, чтобы не было циклов и обратных вызовов, просто где-то здесь.
const Timeline = () => {
const { groupedBookings, dates, setSelectedBooking } = useContext(
TimelineSettingsContext
);
//<--- call hook
function handleBookingSelected(booking) {
setSelectedBooking(booking.LfdNr);
}
//<--- or here
Я думаю, что это как-то связано с тем, как реакция отслеживает хуки, поэтому, по сути, одна из главных вещей заключается в том, что количество вызовов хука должно быть одинаковым для всех рендереров и даже иногда вызывать хуки на картах и т. д., как в вашем примере. может выдавать постоянное количество вызовов перехвата в течение необходимого периода времени, но это не гарантия, поэтому реакция предупреждает об этом. Так что это как бы противоречит дизайну реагирования, поэтому не стоит головной боли.
В вашей ситуации для первого вызова легко переместить вызов в перехватчик за пределами любых обратных вызовов, поскольку аргументами являются только текущая дата, поэтому она одинакова для всех элементов, которые будут повторяться в функции карты. Но второй обратный вызов более сложен. Но если вы хотите использовать здесь хук, вы можете вернуть функцию из хука. Таким образом, по сути, сам хук может получить все переменные, необходимые для реакции, поэтому другие хуки, включая значения контекста, как в вашем случае, возвращают функцию, которая принимает обычные переменные, а затем возвращает значение. Так может выглядеть что-то вроде
export function useCalculatePercentageWithContext() {
const { timelineStart, timelineLength, timelineScale } = useContext(
TimelineSettingsContext,
);
const calculatePercentage = (start, end) => {
//...
return { startPosition, width };
};
return calculatePercentage;
}
и поскольку вычисление процента — это обычная функция, вы можете вызывать ее где угодно, поэтому используйте ее как
const Timeline = () => {
const { groupedBookings, dates, setSelectedBooking } = useContext(
TimelineSettingsContext
);
const calculatePercentage = useCalculatePercentageWithContext();
//....
{category.bookings.map((booking, idx) => {
const { startPosition, width } = calculatePercentage(
new Date(booking.Beginn_Datum),new Date(booking.Ende_Datum))...}
И это будет работать нормально, но, на мой взгляд, наличие всех этих хуков может быстро надоесть, и каждый раз, когда вы хотите добавить какую-то функциональность, вам придется создавать еще один крючок, использующий несколько хуков и т. д.
Если бы я проводил рефакторинг, я бы вызвал контекстный хук в компоненте, имел бы где-нибудь нормальную функцию и просто передал бы ей аргументы, в том числе из контекста. Что-то вроде
export function calculatePercentage({
timelineStart,
timelineLength,
timelineScale,
startDate,
endDate,
}) {
///... logic
return { startPosition, width };
}
затем в временной шкале
const { timelineStart, timelineLength, timelineScale } = useContext(
TimelineSettingsContext,
);
и
{category.bookings.map((booking, idx) => {
const { startPosition, width } = calculatePercentage({
start:new Date(booking.Beginn_Datum),
end:new Date(booking.Ende_Datum),
timelineStart,
timelineLength,
timelineScale,
});
и я думаю, что так будет намного проще.
в реакции важно, чтобы вы не вызывали перехваты за пределами тела верхнего уровня функционального компонента или даже перехвата. Так что, если вы вызовете его на карте, это не сработает. Теоретически, я думаю, это возможно, если длина массива фиксирована в течение определенного жизненного цикла, но они не предназначены для такого использования. Если вы просто посмотрите правила реагирования перехватчиков, вы увидите хорошее объяснение. Но если вы добавите остальную часть кода, где вы вызываете хук, вы сможете это изучить.
о, это кажется интересным, но с моими нынешними знаниями, боюсь, я не пойму этого, если посмотрю код через несколько недель. Поэтому я думаю, что самое простое решение — передать недостающие свойства в функцию вместо использования перехватчика useContext.
ахах да, это интересно, но и неприятно, я бы честно предложил использовать специальные хуки только в тех ситуациях, когда это чрезвычайно полезно. В ответ я добавил, что считаю, что гораздо проще и, на мой взгляд, лучшее решение — просто получить контекстные переменные в компоненте и передать их функции.
Я добавлю пример
хаха, ладно, вот откуда я пришел. Но имеет смысл. Я вернусь к этому.
эта ошибка исчезла, но теперь появляется: Ошибка: React Hook "useCalculatePercentage" не может быть вызван внутри обратного вызова. React Hooks необходимо вызывать в функциональном компоненте React или в пользовательской функции React Hook. Я добавлю вызывающий компонент в свой пост