我正在实现一个井字游戏的客户端.

我有两份Typescript 文件:

  • TicTacToe.tsx:Main Display,包含一个Switch语句,根据状态显示输入或游戏屏幕.
  • TicTacTieAPI.tsx:游戏画面+后端;如果被调用,则建立一个WebSocket连接,并初始化客户端游戏状态.

在测试时,我得到以下错误:

TicTacToeAPI.tsx:5警告:Reaction检测到TicTacToe调用的Hook的顺序发生了更改.如果不进行修复,这将导致错误和错误.欲了解更多信息,请阅读《钩子规则:https://reactjs.org/link/rules-of-hooks

Previous render Next render
1. useState useState
2. useState useState
3. useState useState
4. useState useRef

我不确定解决这个问题的标准方法是什么.

以下是这些文件:

TicTacToe.tsx

import React, { useState } from "react";
import TicTacToeAPI from "./TicTacToeAPI";

export default function TicTacToe() {
  const [menuState, setMenuState] = useState("login");

  const [nickname, setNickname] = useState("");
  const [roomNumber, setRoomNumber] = useState(0);

  return (
    <div className="TicTacToe">
      <h1>Tic Tac Toe</h1>
      {(() => {
        switch (menuState) {
          case "login":
            return input(setMenuState, setNickname, setRoomNumber);
          case "game":
            return game(setMenuState, roomNumber, nickname);
          case "end":
            return end();
          default:
            return null;
        }
      })()}
    </div>
  );
}

function game(
  setMenuState: React.Dispatch<React.SetStateAction<string>>,
  roomNumber: number,
  nickname: string
) {
  return (
    <>
      <div id="metaData">
        <p>Room number: {roomNumber}</p>
        <p>Nickname: {nickname}</p>
      </div>
      {TicTacToeAPI(nickname, roomNumber)}
      <div id="abandonButton">
        <button onClick={() => setMenuState("login")}>rage quit</button>
      </div>
    </>
  );
}

function end() {
  return <p>Game ended</p>;
}

function input(
  setMenuState: React.Dispatch<React.SetStateAction<string>>,
  setNickname: React.Dispatch<React.SetStateAction<string>>,
  setRoomNumber: React.Dispatch<React.SetStateAction<number>>
) {
  const [formData, setFormData] = useState({
    nickname: "genericName",
    roomNumber: 420,
  });

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData({ ...formData, [e.target.id]: e.target.value });
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    // validate input
    if (!formData.nickname || !formData.roomNumber) {
      alert("Please enter a nickname and room number");
      return;
    }
    // set parameters
    setNickname(formData.nickname);
    setRoomNumber(formData.roomNumber);
    // change menu state
    setMenuState("game");
  };

  return (
    <div id="playerInfoInput">
      <form className="playerInfo" onSubmit={handleSubmit}>
        <h3>Nickname</h3>
        <input
          type="text"
          id="nickname"
          value={formData.nickname}
          onChange={handleInputChange}
        />
        <br />
        <h3>Room number</h3>
        <input
          type="number"
          id="roomNumber"
          value={formData.roomNumber}
          onChange={handleInputChange}
        />
        <br />
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}

TicTacToeAPI.tsx

import React, { useEffect, useRef, useState } from "react";

export default function TicTacToeAPI(nickname: string, roomNumber: number) {
  //websocket
  const wsRef = useRef(new WebSocket("ws://localhost:8080/ticTacToe"));

  //set other data
  const [authToken, setAuthToken] = useState("");
(...)
  //websocket
  useEffect(() => {
    //configure message handler
    wsRef.current.onmessage = (message) => {
      handleMessage(message, stateData);
    };

    // Cleanup WebSocket connection when the component unmounts
    return () => {
      wsRef.current.close();
    };
  }, []);

  return (<p>board</p>)
}

根据我的研究,因为钩子是在它们自己的功能组件中,所以这种切换应该不是问题,但显然我误解了一些东西.

推荐答案

我很惊讶您没有收到从嵌套函数调用钩子的Reaction Hooks错误.TicTacToeAPIinputgame都不是有效的react 功能组件.

更新代码,使这些函数是有效的react 组件,并将它们呈现为JSX,而不是直接调用.Reaction功能组件获取单个props 对象,各种props 可以从它解构.

示例:

TicTacTieAPI.tsx

import React, { useEffect, useRef, useState } from "react";

interface TicTacToeApiProps {
  nickname: string;
  roomNumber: number;
};

export default function TicTacToeAPI({ nickname, roomNumber }: TicTacToeApiProps) {
  //websocket
  const wsRef = useRef(new WebSocket("ws://localhost:8080/ticTacToe"));

  //set other data
  const [authToken, setAuthToken] = useState("");
(...)
  //websocket
  useEffect(() => {
    //configure message handler
    wsRef.current.onmessage = (message) => {
      handleMessage(message, stateData);
    };

    // Cleanup WebSocket connection when the component unmounts
    return () => {
      wsRef.current.close();
    };
  }, []);

  return (<p>board</p>)
}

TicTacToe.tsx

import React, { useState } from "react";
import TicTacToeAPI from "./TicTacToeAPI";
interface GameProps {
  setMenuState: React.Dispatch<React.SetStateAction<string>>;
  roomNumber: number;
  nickname: string;
}

function Game({ setMenuState, roomNumber, nickname }: GameProps) {
  return (
    <>
      <div id="metaData">
        <p>Room number: {roomNumber}</p>
        <p>Nickname: {nickname}</p>
      </div>
      <TicTacToeAPI nickname={nickname} roomNumber={roomNumber} /> // <-- JSX!
      <div id="abandonButton">
        <button onClick={() => setMenuState("login")}>rage quit</button>
      </div>
    </>
  );
}
function End() {
  return <p>Game ended</p>;
}
interface InputProps {
  setMenuState: React.Dispatch<React.SetStateAction<string>>;
  setNickname: React.Dispatch<React.SetStateAction<string>>;
  setRoomNumber: React.Dispatch<React.SetStateAction<number>>;
}

function Input({ setMenuState, setNickname, setRoomNumber }: InputProps) {
  const [formData, setFormData] = useState({
    nickname: "genericName",
    roomNumber: 420,
  });

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData({ ...formData, [e.target.id]: e.target.value });
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    // validate input
    if (!formData.nickname || !formData.roomNumber) {
      alert("Please enter a nickname and room number");
      return;
    }
    // set parameters
    setNickname(formData.nickname);
    setRoomNumber(formData.roomNumber);
    // change menu state
    setMenuState("game");
  };

  return (
    <div id="playerInfoInput">
      <form className="playerInfo" onSubmit={handleSubmit}>
        <h3>Nickname</h3>
        <input
          type="text"
          id="nickname"
          value={formData.nickname}
          onChange={handleInputChange}
        />
        <br />
        <h3>Room number</h3>
        <input
          type="number"
          id="roomNumber"
          value={formData.roomNumber}
          onChange={handleInputChange}
        />
        <br />
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}
export default function TicTacToe() {
  const [menuState, setMenuState] = useState("login");

  const [nickname, setNickname] = useState("");
  const [roomNumber, setRoomNumber] = useState(0);

  const renderUi = () => {
    switch (menuState) {
      case "login":
        return (
          <Input
            setMenuState={setMenuState}
            setNickname={setNickname}
            setRoomNumber={setRoomNumber}
          />
        );

      case "game":
        return <Game {...{ setMenuState, roomNumber, nickname }} />;

      case "end":
        return <End />;

      default:
        return null;
    }
  };

  return (
    <div className="TicTacToe">
      <h1>Tic Tac Toe</h1>
      {renderUi()}
    </div>
  );
}

Typescript相关问答推荐

Angular -使用Phone Directive将值粘贴到控件以格式化值时验证器不工作

如何从具有给定键列表的对象类型的联合中构造类型

如何访问Content UI的DatePicker/中的sx props of year picker?'<>

为什么从数组中删除一个值会删除打字错误?

如何将联合文字类型限制为文字类型之一

输入元素是类型变体的对象数组的正确方法是什么?'

打印脚本丢失函数的泛型上下文

更新为Angular 17后的可选链运算符&?&问题

对数组或REST参数中的多个函数的S参数进行类型判断

如何编写一个类型脚本函数,将一个对象映射(转换)为另一个对象并推断返回类型?

缩小并集子键后重新构造类型

使用数组作为类型中允许的键的列表

如何合并两个泛型对象并获得完整的类型安全结果?

如何在typescript中为this关键字设置上下文

有没有办法在Zod中使用跨字段验证来判断其他字段,然后呈现条件

类型推断对React.ForwardRef()的引用参数无效

在Cypress中,您能找到表格中具有特定文本的第一行吗?

为什么上下文类型在`fn|curred(Fn)`的联合中不起作用?

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

使用泛型以自引用方式静态键入记录的键