Я пытаюсь использовать ссылки динамически, но получаю сообщение об ошибке «Неверный вызов перехватчика. Перехватчики можно вызывать только внутри тела функционального компонента» при использовании useRef. Здесь:
const [subsistemaPlanetario, setSubsistemaPlanetario] = useState([]);
const planetRefs = useRef({});
useEffect(() => {
async function fetchSubsistemaPlanetario() {
try {
const fetchedSubsistemaPlanetario = await getSubsistemaPlanetario();
setSubsistemaPlanetario(fetchedSubsistemaPlanetario);
fetchedSubsistemaPlanetario.forEach((planeta) => {
const camelCaseSlug = planeta.padre.slug.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
planetRefs.current[camelCaseSlug] = useRef(); // <------THIS LINE DROP AN ERROR
});
} catch (error) {
console.error(error);
}
}
fetchSubsistemaPlanetario();
}, []);
ВЕСЬ КОМПОНЕНТ:
import {useFrame} from '@react-three/fiber';
import React, {useRef, useEffect, useState} from 'react';
import {Planet} from './Planet.jsx';
import {Satellite} from './Satellite.jsx';
import {Orbiter} from './utils/Orbiter.js';
import {calculateOrbitalPeriod} from './utils/calculateOrbitalPeriod.js';
import {getSubsistemaPlanetario} from './utils/getSubsistemaPlanetario.js';
export const SubsistemaPlanetario = function SubsistemaPlanetario(props) {
let running = true;
let stopRunning = () => (running = false);
let startRunning = () => (running = true);
const [subsistemaPlanetario, setSubsistemaPlanetario] = useState([]);
const planetRefs = useRef({});
useEffect(() => {
// Obtener el subsistema planetario cuando el componente se monta
async function fetchSubsistemaPlanetario() {
try {
const fetchedSubsistemaPlanetario = await getSubsistemaPlanetario();
setSubsistemaPlanetario(fetchedSubsistemaPlanetario);
fetchedSubsistemaPlanetario.forEach((planeta) => {
const camelCaseSlug = planeta.padre.slug.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
planetRefs.current[camelCaseSlug] = useRef();
console.info(planetRefs);
});
} catch (error) {
console.error(error);
}
}
fetchSubsistemaPlanetario();
}, []);
return (
<>
{subsistemaPlanetario.map((planetaPadre, index) => (
<Planet
key = {index}
scale = {0.5}
ref = {planetRefs.current[index]}
stopRunning = {stopRunning}
startRunning = {startRunning}
textureType = "haumea"
linkTo = "areas"
linkToLabel = "Areas"
/>
))}
</>
);
};
КОМПОНЕНТ ПЛАНЕТЫ
import {forwardRef, useRef, useEffect, useContext, useState} from 'react';
import PropTypes from 'prop-types';
import {useTexture} from '@react-three/drei';
import {useFrame} from '@react-three/fiber';
import barba from '@barba/core';
import {solapaContentAbrir, solapaContentCerrar} from './utils/Solapa.js';
import {planets} from './utils/arrayTexturas.js';
// Define un objeto que mapea los tipos de textura a las rutas de los archivos de textura.
const textureMap = {};
for (const planet of planets) {
textureMap[planet] = `./app/themes/sage/resources/scripts/cosmos/components/textures/${planet}-512.jpg`;
}
export const Planet = forwardRef(function Planet(props, ref) {
// Obtén la ruta de la textura según el tipo especificado en props.textureType.
const texturePath = textureMap[props.textureType] || textureMap.sand;
const texture = useTexture(texturePath);
let rotationX = Math.random();
let rotationY = Math.random();
useFrame((state, delta) => {
ref.current.rotation.x += rotationX * delta;
ref.current.rotation.y += rotationY * delta;
});
return (
<mesh
{...props}
ref = {ref}
castShadow
receiveShadow
onPointerEnter = {(event) => {
props.stopRunning();
document.body.style.cursor = 'pointer';
solapaContentAbrir('Sección', props.linkToLabel);
event.stopPropagation();
}}
onPointerLeave = {(event) => {
props.startRunning();
document.body.style.cursor = 'default';
solapaContentCerrar();
event.stopPropagation();
}}
onClick = {(event) => {
barba.go(props.linkTo);
}}
>
<sphereGeometry />
<meshStandardMaterial map = {texture} />
</mesh>
);
});
Planet.propTypes = {
stopRunning: PropTypes.func,
startRunning: PropTypes.func,
textureType: PropTypes.oneOf(['haumea', 'mars', 'neptune', 'venus', 'mercury', 'jupiter', 'saturn']),
userData: PropTypes.object,
radius: PropTypes.number,
linkTo: PropTypes.string,
linkToLabel: PropTypes.string,
};
Любая помощь приветствуется.
Пожалуйста, поделитесь кодом всего файла, то есть начиная с первой строки, чтобы мы могли точно видеть, где он используется.
Попытка создать ссылку в объекте PlanetRefs для каждой «планеты». (Не уверен, что это правильно)
Готово, я отредактировал вопрос.
Вы пытаетесь нарушить правила. Смотрите это. Если вы пытаетесь запомнить данные, используйте вместо этого useMemo.
это часто возникает, но я не могу найти обманщиков. используйте ссылку на массив, объект или карту и т. д.
Что ты вообще пытаешься? Зачем вам нужен этот реферал за пределами Planet? Учитывая ваш код, я не вижу причины, по которой вам нужно хранить ссылку за пределами самой Планеты. Единственное место, где вы его используете, это внутри самой Планеты.





Не уверен, чего именно вы пытаетесь достичь здесь.
ref = {planetRefs.current[index]}
ничего не сделает, поскольку planetRefs.current является пустым объектом и останется пустым объектом, поскольку вы не присваиваете ему никакого значения.
planetRefs.current[camelCaseSlug] = useRef();
также ничего не сделает, поскольку вы не можете использовать хуки реагирования внутри функции, которая не является компонентом реагирования.
Кстати, почему вы назначаете значение ref как
camelCaseSlug, но при рендеринге пытаетесь получить к нему доступ с помощьюindex?
Я думаю, вам следует вместо этого использовать ссылку обратного вызова, чтобы получить доступ к визуализируемым элементам:
ref = {(ref) => {
planetRefs.current[index] = ref;
}}
Примечание. Не забудьте обернуть компонент PlanetforwardRef.
Вы можете полностью удалить часть planetRefs.current[camelCaseSlug] = useRef();. Если вы предпочитаете хранить refs в planetRefs в виде слизней, просто немного измените ссылку обратного вызова:
ref = {(ref) => {
const camelCaseSlug = planetaPadre.padre.slug
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
planetRefs.current[camelCaseSlug] = ref;
}}
Спасибо большое за ответ 🙏. Боюсь, это слишком много для моего уровня. Я не понимаю ссылку обратного вызова. Когда я реализую это, ссылка недоступна в компоненте Planet. Я ценю ваши усилия, правда.
@aitor Чтобы получить доступ к ссылке, вам нужно использовать функцию forwardRef в вашем компоненте Planet. Пожалуйста, проверьте: act.dev/reference/react/forwardRef
Да, оно у меня есть: export const Planet = forwardRef(function Planet(props, ref) {}
@aitor Не забудьте передать значение ref понравившемуся компоненту. Например <div ref = {ref}>{...}</div>. Ref должен быть присвоен какому-то элементу.
@aitor Было бы лучше, если бы ты просто показал мне свой Planet-компонент :)
пишу комментарий! одна минута :)
Давайте продолжим обсуждение в чате.
@aitor Пожалуйста, просто отредактируйте свой вопрос и вставьте компонент Planet :)
@aitor Спасибо за редактирование. Я не уверен, почему вы хотите передавать ссылки динамически. Вы хотите иметь доступ к компоненту Planet от родителя? Если нет, вам следует переместить использование useRef в компонент Planet, как это показано на: docs.pmnd.rs/react-three-fiber/getting-started/introduction (посмотрите здесь, useRef используется в компоненте Box, как ваш Planet).
Извините, я только что увидел ваш комментарий. Да, ты прав. Я уже видел, в чем была моя ошибка. Я пытаюсь обновить позиции родительского компонента, когда в этом нет необходимости. Я могу выполнить расчеты в компоненте «Планета». Это также сделает весь код чище. Я очень благодарен вам за всю помощь, которую я получил. Это было очень полезно.
Вы вызываете перехватчик условно для своего компонента.
https://legacy.reactjs.org/docs/hooks-rules.html
Не вызывайте хуки внутри циклов, условий или вложенных функций. Вместо этого всегда используйте хуки на верхнем уровне вашей функции React, прежде чем делать какие-либо ранние возвраты. Следуя этому правилу, вы гарантируете, что хуки будут вызываться в одном и том же порядке каждый раз при рендеринге компонента.
Как уже упоминалось здесь, вы просто не можете использовать перехватчики в любой форме потока управления.
Так что нет if, else, for, while, forEach, callback, ...
Вы можете использовать ref = {} с обратным вызовом, например:
<Planet
key = {index}
ref = {(ref) => {planetRefs[index] = ref}}
но кроме этого, нет возможности сохранить рефери такими, какие они у вас есть.
Редактировать: Я до сих пор не уверен, чего вы здесь пытаетесь достичь, но нет необходимости иметь ссылку на Планету за пределами самой Планеты. (Честно говоря, реферал может вообще не понадобиться) Вы можете просто обновить ротацию в Planet и покончить с этим.
export const Planet = function Planet(props, ref) {
// ...
const ref = useRef();
let rotationX = Math.random();
let rotationY = Math.random();
useFrame((state, delta) => {
ref.current.rotation.x += rotationX * delta;
ref.current.rotation.y += rotationY * delta;
});
return (
<mesh
{...props}
ref = {ref}
// ...
>
// ...
);
};
// ...
Альтернативно, если рефы действительно нужны снаружи, а это, на мой взгляд, редко, создайте массив/объект и передайте его компоненту Planet.
const planets = useRef({});
// ...
planets.forEach((planet) => {
return <Planet key = {planet.slug} planets = {planets.current} ... />
});
function Planet(props) {
const ref = useRef();
useEffect(() => {
// Add it to the list of planets when component is rendered
props.planets[props.slug] = ref;
}, () => {
// Remove when no longer needed
props.planets[props.slug] = undefined;
});
// ...
}
Вы сможете получить к нему доступ, используя
planets.current[slug].current
Проблема в том, что мне нужно, чтобы ссылка была доступна в компоненте планеты. Если внутри Planet да, то console.info(ref); Возвращает функцию обратного вызова вместо ссылки.
@aitor Я добавил решение, реализующее эту проблему. Но я действительно думаю, что вам следует подумать об этом, если вам действительно нужен рефери за пределами Планеты. Обычно это показатель плохой сепарации (по моему опыту)
Абсолютно прав. Большое спасибо. Правильный подход — хранить обновления позиций внутри компонента.
Чего вы пытаетесь достичь этой линией?