在不实际更改的情况下反应更改值

时间:2020-11-12 03:02:03

标签: javascript reactjs

我正在尝试制作一款像紫色对的紫宫游戏。我想做的是,每当单击两个值不相等的元素时,卡就会自动关闭,但是发生的事情却有所不同。每当我单击两张错误的卡,然后选择另一张卡时,即使我没有编写任何代码来执行此操作,其值也会更改。这非常令人沮丧,我无处可去尝试解决这个问题。请帮我解决这个问题。

原始密码笔链接 Click here to visit

我认为问题出在handleClick函数中。

enter image description here

function Card(props) {
  const [show, setShow] = useState(props.chosen);
  handleClick = (e) => {
    if (props.chosen) {
      setShow(true);
    } else {
      props.onClick();
      setShow(!show);
    }
  };
  const style1 = {
    background: "grey",
    transform: `rotateY(${!show ? 0 : 180}deg)`
  };
  const style2 = {
    background: "#aaa",
    transform: `rotateY(${show ? 0 : 180}deg)`
  };
  return (
    <div class="container" onClick={handleClick}>
      <div className="flip" style={style1}></div>
      <div className="flip" style={style2}>
        {props.value}
      </div>
    </div>
  );
}

class GameBoard extends React.Component {
  constructor() {
    super();
    this.state = {
      score: 0,
      time: 0,
      list: [...generateObList(), ...generateObList()],
      count: 0
    };
  }

  handleClick = async (id) => {
    await this.clickBtn(id);
    const list = _.cloneDeep(this.state.list);
    const current = list.find((a) => a.id === id);
    for (let x of list) {
      if (
        x.clicked &&
        x.id != id &&
        x.value == list.find((a) => a.id == id).value
      ) {
        x.chosen = true;
        x.clicked = false;
        current.chosen = true;
        current.clicked = false;
        this.setState((prev) => ({
          list: prev.list.map((el) =>
            el.id === id ? current : el.value === current.value ? x : el
          ),
          score: prev.score + 1
        }));
      } else if (this.state.count % 2 == 0 && x.clicked) {
        console.log("Entered");
        current.clicked = false;
        x.clicked = false;
        this.setState((prev) => ({
          list: prev.list.map((el) =>
            el.id === id ? current : el.value === current.value ? x : el
          )
        }));
      }
    }
  };

  clickBtn = (id) => {
    const current = _.cloneDeep(this.state.list).find((e) => e.id === id);
    let deClick = current.clicked;
    current.clicked = !current.clicked;
    this.setState((prev) => ({
      list: prev.list.map((el) => (el.id === id ? current : el)),
      count: prev.count + (deClick ? -1 : 1)
    }));
  };

  render() {
    const boardStyle = {
      gridTemplateColumns: `repeat(5, 1fr)`,
      gridTemplateRows: `repeat(5,1r)`
    };
    let list = this.state.list.map((n) => (
      <Card
        value={n.value}
        onClick={(e) => {
          this.handleClick(n.id);
        }}
        chosen={n.chosen}
        clicked={n.clicked}
      />
    ));
    return (
      <div class="gameBoard" style={boardStyle}>
        {list}
      </div>
    );
  }
}

1 个答案:

答案 0 :(得分:1)

handleClick函数中存在一些严重问题。出现问题的主要原因是您设法以某种方式将列表项替换为其他列表项。

此行中发生覆盖问题。我不完全确定为什么会导致此问题,但确实如此。

          list: prev.list.map((el) =>
            el.id === id ? current : el.value === current.value ? x : el
          )

如果仅将其替换为以下内容,则该问题将消失:

          list: prev.list.map((el) => el.clicked ? {...el, clicked:false}: el)

clickBtn不是异步函数,因此对其使用await不会执行任何操作。如果要等待状态更改,则需要兑现承诺。我已经有一段时间没有使用类组件了,所以我不知道这是否是使用它们的一种特别鼓励的方式,但是可能还有其他方式:

      await new Promise((resolve) =>
        this.setState(
          (prev) => ({
            list: prev.list.map((card) =>
              card.clicked ? { ...card, clicked: false } : card
            ),
            freeze: false
          }),
          () => resolve()
        )
      );

要注意的另一件事是,您保持了GameBoard中单击了哪些卡的状态,因此没有理由让Card保持有状态,实际上,这就是卡不会翻转的原因。< / p>

通过将卡的开始从使用useState更改为props值,即可解决:

  const show = props.chosen||props.clicked;
  handleClick = (e) => {
    if (props.chosen) {
    } else {
      props.onClick();
    }
  };

https://codepen.io/ZachHaber/pen/yLJGayv


重构:

我在进行故障排除时进行了一些重构,以使所有工作正常进行。

我也有一些有趣的实现逻辑,这就是为什么当用户猜错时我添加了具有超时功能的翻转行为的原因。

const {shuffle} = _;

const numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let id = 0;
const generateObList = () => {
  return numbersList.map((e) => ({
    // Use something more guaranteed to not be the same value
    // Math.random *could* clash, but is very unlikely to.
    id: id++,
    value: e,
    chosen: false,
    clicked: false
  }));
};
// Using a shuffle algorithm here to shuffle the tiles.
const generateList = () => shuffle([...generateObList(), ...generateObList()]);

function Card(props) {
  const show = props.chosen || props.clicked;
  // Remove the local state here, it's just problematic
  // Let the parent control the state.
  const handleClick = (e) => {
    if (props.chosen) {
    } else {
      props.onClick();
    }
  };
  const style1 = {
    background: "grey",
    transform: `rotateY(${!show ? 0 : 180}deg)`
  };
  const style2 = {
    background: "#aaa",
    transform: `rotateY(${show ? 0 : 180}deg)`
  };
  return (
    <div className="container" onClick={handleClick}>
      <div className="flip" style={style1}></div>
      <div className="flip" style={style2}>
        {props.value}
      </div>
    </div>
  );
}

class GameBoard extends React.Component {
  constructor() {
    super();
    this.state = {
      score: 0,
      time: 0,

      list: generateList(),
      count: 0,
      freeze: false
    };
  }
  timerId = null;
  performUpdate = (id) => {
    // Flip the relevant card in the list
    const list = this.state.list.map((card) =>
      card.id === id ? { ...card, clicked: !card.clicked } : card
    );
    // Get the active card
    const current = list.find((card) => card.id === id);
    // Get all cards that match the current value
    let matches = list.filter((card) => card.value === current.value);
    // Somehow the card was already chosen
    // Likely can remove this because this condition is also in the children
    if (matches.every((card) => card.chosen)) {
      return; // invalid click, don't do anything
    }
    // the matches are all clicked, now they are valid to be chosen!
    if (matches.every((card) => card.clicked)) {
      this.setState((prev) => ({
        list: list.map((card) =>
          card.value !== current.value
            ? card
            : { ...card, clicked: false, chosen: true }
        ),
        score: prev.score + 1,
        count: prev.count + 1
      }));
      return;
    }
    // There are 2 cards clicked, reset them after a timer!
    if (list.filter((card) => card.clicked).length === 2) {
      // Have to post the current click state change -
      // Make it so it will flip over the tile briefly
      this.setState((prev) => ({ list, count: prev.count + 1, freeze: true }));
      // Then after a timeout, flip it back over
      this.timerId = setTimeout(() => {
        this.setState((prev) => ({
          list: prev.list.map((card) =>
            card.clicked ? { ...card, clicked: false } : card
          ),
          freeze: false
        }));
      }, 500);
      return;
    }
    // At this point it's just a normal click:
    // set the new adjusted list, and increment count.
    this.setState((prev) => ({
      list,
      count: prev.count + 1
    }));
  };
  handleClick = (id) => {
    // Waiting for board to flip tiles over currently. User is impatient
    // Could just return in this case if you want users to wait
    if (this.state.freeze) {
      clearTimeout(this.timerId);
      // Perform the update to clear the clicked status and freeze status
      // and wait for it to resolve before continuing
      this.setState(
        (prev) => ({
          list: prev.list.map((card) =>
            card.clicked ? { ...card, clicked: false } : card
          ),
          freeze: false
        }),
        () => this.performUpdate(id)
      );
    } else {
      this.performUpdate(id);
    }
  };
  reset = () => {
    this.setState({
      count: 0,
      score: 0,
      time: 0,
      freeze: false,
      list: generateList()
    });
  };
  render() {
    const boardStyle = {
      gridTemplateColumns: `repeat(5, 1fr)`,
      gridTemplateRows: `repeat(5,1r)`
    };
    let list = this.state.list.map((card) => (
      <Card
        key={card.id}
        value={card.value}
        onClick={(e) => {
          this.handleClick(card.id);
        }}
        chosen={card.chosen}
        clicked={card.clicked}
      />
    ));
    return (
      <div>
        <div className="gameBoard" style={boardStyle}>
          {list}
        </div>
        <div>
          move count: {this.state.count}
          <br />
          score: {this.state.score}
        </div>
        {this.state.score === this.state.list.length / 2 && (
          <button onClick={this.reset}>Reset</button>
        )}
      </div>
    );
  }
}
ReactDOM.render(<GameBoard/>,document.getElementById('root'))
.container {
  position: relative;
  width: 100px;
  height: 100px;
  background: #eee;
  cursor: pointer;
}
.gameBoard {
  display: grid;
}
.container .flip {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 100px;
  font-size: 4em;
  transition: transform 400ms;
  transition-timing-function: cubic-bezier(0.1, 0.39, 0.3, 0.95);
  backface-visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>