来自 Angular,我很难用 React 构建一个简单的演示,因为我遇到了无限循环和其他副作用。
我有一个
GamePage.tsx
(容器组件),用户可以从主页导航。加载此组件时,我使用 Tanstack 查询(包装在自定义挂钩中的代码)从 API 获取一些数据。
根据结果,我将结果存储在组件的 state 属性中,并用它显示一个子组件。
但是,虽然我认为存储从 API 返回的数据似乎是保存在组件状态中的一个不错的选择,但通过设置它会触发无限循环。 我是否必须在 GamePage.tsx 中创建另一个子组件才能使用/调用自定义挂钩?这似乎有点过于冗长,并且并没有真正带来好处,因为容器组件已经相当小了。 考虑到下面的情况(容器组件 -> 获取数据 -> 初始化子组件)我的解决方案中应该更改哪些内容以遵循“React 方式”?从文档中我找不到唯一/通用的答案。
定制挂钩
const useWordByDifficulty = ({ difficulty }: { difficulty: Difficulty }) => {
const wishedLength = mapDifficultyToLength(difficulty);
return useQuery({
queryKey: [],
queryFn: () => httpFetch(`API_URL`)
});
};
游戏页面.tsx
const GamePage = () => {
const { state }: { state: { difficulty: Difficulty } } = useLocation();
// While I think the state here might be a good choice, it triggers an infinite loop setting it in the code below
//const [gameWord, setGameWord] = useState<string | null>(null);
let reqStatus: HttpResponseStatus | null = null;
let gameWord: string | null = null;
// useEffect(() => { <-- This is not permitted as useWordByDifficulty is a custom hook
let active = true;
// The API is called twice
const { data, error, isPending, isError } = useWordByDifficulty(state)
if (active) {
if (isPending) {
reqStatus = 'isPending';
} else if (isError) {
reqStatus = 'isError';
} else {
// If I set the word here, I get an infinite loop as the component re-render
// setGameWord(data);
reqStatus = 'isSuccess';
}
}
return () => {
// cleanup
active = false;
}
// }, [state]);
return (
<div>
<h1>Game Page</h1>
<h3>Diff: {state.difficulty}</h3>
{reqStatus === 'isPending' && <div>Loading...</div>}
{reqStatus === 'isError' && <div>An error occurred...</div>}
{reqStatus === 'isSuccess' && <GameWord gameWord={gameWord} difficulty={state.difficulty}></GameWord>}
</div>
)
};
将自定义挂钩保留在 useEffect 之外(另请参阅挂钩规则)并从组件的根目录调用它们。然后,您可以从 useEffect 内部调用结果数据,并将数据添加到 useEffect 的依赖项数组中。如果您的唯一目的是将数据写入变量,那么您确实可以跳过 useEffect 和 useState。
由于您使用
<StrictMode>
进行调试(用于检查缺少的效果清理),API 可能会被触发两次,在这种情况下它会按预期工作。否则,这可能是由于自定义钩子的错误实现(使用重新渲染运行)引起的,但由于您只返回广泛使用和测试的钩子的结果,因此情况不应该如此。