我无法让用户动态地将对象添加到使用 useSharedValues() 渲染到屏幕的数组中
这是我的主屏幕,其中对象被添加到数组中
import { useRoute } from '@react-navigation/native';
import React, { useState } from 'react';
import { Pressable, View } from 'react-native';
import PlayerView from '../../components/PlayerView/PlayerView';
import deck from './deck';
import styles from './styles';
export default function GameScreen() {
const names = route.params?.names;
const [players, setPlayers] = useState(() => initPlayers());
const [round, setRound] = useState(1);
const [currentDeck, setCurrentDeck] = useState(deck);
const [tlayout, setLayout] = useState([]);
const [playerTurnCounter, setPlayerTurnCounter] = useState(0); // used to keep track of whose turn it is
const [deckSelected, setDeckSelected] = useState(false);
function initPlayers() {
const players = [];
for (let i = 0; i < names.length; i++) {
players.push({ id: i, name: names[i].name, points: 0, hand: [], subHand: [[], [], [], []] });
}
return players;
}
function handleDeckPress() {
if (deckSelected) {
const rand = Math.floor(Math.random() * currentDeck.length);
const card = currentDeck[rand];
currentDeck.splice(rand, 1);
setCurrentDeck(currentDeck);
addDeckCardToPlayerHand(card);
setDeckSelected(false);
} else if (!deckSelected) {
setDeckSelected(true);
}
}
const addDeckCardToPlayerHand = (card) => {
const test = players[playerTurnCounter].hand;
const newArray = [...test, { card, id: 3 }];
setPlayers(
players.map((player) => {
if (player.id === playerTurnCounter) {
return { ...player, hand: newArray };
}
return player;
}),
);
};
return (
<View>
<Pressable onPress={() => handleDeckPress()} hitSlop={{ bottom: 10 }}>
{currentDeck.map((card, index) => (
<View
onLayout={(event) => {
const { x, y, width, height } = event.nativeEvent.layout;
}}
/>
))}
</Pressable>
<View
onLayout={(event) => {
event.target.measure((x, y, width, height, pageX, pageY) => {
const t = tlayout;
t.push({ x, y, width, height, pageX, pageY });
// console.log('subhand one: ', t);
setLayout(t);
});
}}
style={styles.subHandOne}
/>
<PlayerView
style={styles.playerView}
players={players}
setPlayers={setPlayers}
playerTurnCounter={playerTurnCounter}
layout={tlayout}
/>
</View>
);
}
包含要渲染的对象数组的“播放器”被传递给以下组件:
import React from 'react';
import { View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import Card from '../Card/Card';
import CardList from '../CardList/CardList';
import styles from './styles';
export default function PlayerView({ players, setPlayers, playerTurnCounter }) {
const player = initPlayer();
function initPlayer() {
for (let i = 0; i < players.length; i++) {
if (players[i].id === playerTurnCounter) {
return players[i];
}
}
return players[0];
}
if (player.hand.length > 0) {
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.cards}>
<CardList players={players} setPlayers={setPlayers} playerTurnCounter={playerTurnCounter}>
{player.hand.map((item) => (
<Card key={item.id} item={item.card} count={item.id} />
))}
</CardList>
</View>
</GestureHandlerRootView>
);
}
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.cards} />
</GestureHandlerRootView>
);
}
CardList(计算手牌数组的位置):
/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable react/no-array-index-key */
/* eslint-disable react/prop-types */
import React, { useState } from 'react';
import { Dimensions, View } from 'react-native';
import { runOnJS, runOnUI, useSharedValue } from 'react-native-reanimated';
import SortableCard from '../SortableCard/SortableCard';
import styles from './styles';
const containerWidth = Dimensions.get('window').width * 2;
export default function CardList({ children, players, setPlayers, playerTurnCounter }) {
const [ready, setReady] = useState(false);
const offsets = children.map(() => ({
order: useSharedValue(0),
width: useSharedValue(0),
height: useSharedValue(0),
x: useSharedValue(0),
y: useSharedValue(0),
originalX: useSharedValue(0),
originalY: useSharedValue(0),
}));
if (!ready) {
return (
<View style={styles.row}>
{children.map((child, index) => {
return (
<View
key={child.key}
onLayout={({
nativeEvent: {
layout: { x, y, width, height }, // these give the original layout positions of the cards (and their sizes) when loaded in
},
}) => {
const offset = offsets[index];
offset.order.value = -1;
offset.width.value = width / children.length + 10;
offset.height.value = height;
offset.originalX.value = x;
offset.originalY.value = y;
runOnUI(() => {
'worklet';
if (offsets.filter((o) => o.order.value !== -1).length === 0) {
runOnJS(setReady)(true);
}
})();
}}>
{child}
</View>
);
})}
</View>
);
}
return (
<View style={styles.container}>
{children.map((child, index) => (
<SortableCard
key={child.key}
offsets={offsets}
index={index}
players={players}
setPlayers={setPlayers}
containerWidth={containerWidth}
playerTurnCounter={playerTurnCounter}>
{child}
</SortableCard>
))}
</View>
);
}
SortableCard 组件包含负责动画手势的代码。
我的问题是,当我尝试将一个对象添加到该数组(称为“hand”)到将“hand”中的对象渲染到屏幕的组件时,我收到错误:
Error: Rendered more hooks than during the previous render
,
当我期待这个添加到数组中的新对象渲染到屏幕上时。
我知道这个错误是由于调用 addDeckCardToPlayerHand 回调时 CardList 中的 offsets 数组中的钩子数量发生变化,从而改变了渲染的钩子数量。所以我的问题是,如果一开始 useSharedValues 的总量未知,如何允许用户动态创建将渲染到屏幕上的对象?
代码中的问题是
useSharedValue
钩子在循环/回调中被调用,这破坏了React的Hooks规则。 (强调我的)
不支持在任何环境中调用Hooks(以use开头的函数) 其他情况,例如:
- 🔴 不要在条件或循环内调用 Hook。
- 🔴 不要在条件
语句之后调用 Hooks。return
- 🔴 不要在事件处理程序中调用 Hooks。
- 🔴不要在类组件中调用Hooks。
- 🔴 不要在传递给
、useMemo
或useReducer
的函数内调用 Hook。useEffect
- 🔴不要在
/try
/catch
块内调用Hooks。finally
如果我正确理解您的代码,那么您似乎正在创建一种“加载骨架”或在设置
children
状态之前对 ready
进行一些预先测量。
useSharedValue
钩子文档,您可以存储数组值。
参见初始值:(强调我的)
您最初想要存储在共享值中的值。它可以是 任何 JavaScript 值,如
、number
或string
,还有数据 结构如boolean
和array
。object
我建议使用带有“子项”数组的单个
useSharedValue
钩子,并且可以在 View
组件的 onLayout
回调处理程序中更新和访问。
示例:
export default function CardList({ children, players, setPlayers, playerTurnCounter }) {
const [ready, setReady] = useState(false);
const offsets = useSharedValue(children.map(() => ({
order: 0,
width: 0,
height: 0,
x: 0,
y: 0,
originalX: 0,
originalY: 0,
}));
if (!ready) {
return (
<View style={styles.row}>
{children.map((child, index) => {
return (
<View
key={child.key}
onLayout={({
nativeEvent: { layout: { x, y, width, height } },
}) => {
const offset = offsets[index];
offset.order.value = -1;
offset.width.value = width / children.length + 10;
offset.height.value = height;
offset.originalX.value = x;
offset.originalY.value = y;
runOnUI(() => {
'worklet';
if (offsets.filter((o) => o.order.value !== -1).length === 0) {
runOnJS(setReady)(true);
}
})();
}}>
{child}
</View>
);
})}
</View>
);
}
return (
<View style={styles.container}>
{children.map((child, index) => (
<SortableCard
key={child.key}
offsets={offsets}
index={index}
players={players}
setPlayers={setPlayers}
containerWidth={containerWidth}
playerTurnCounter={playerTurnCounter}>
{child}
</SortableCard>
))}
</View>
);
}