[Vue warn]: Duplicate keys found during update: Make sure keys are unique

[Vue warn]: Duplicate keys found during update: Make sure keys are unique

我正在 Vue 中构建一个井字游戏,当我尝试重置棋盘时遇到此错误。

[Vue 警告]:更新期间发现重复键:“确保键是唯一的。”

棋盘在视觉上开始变形。我在屏幕上出现额外的方块。

我错过了什么?我的状态设置正确吗?

<template>
  <h2>{{ msg }}</h2>
  <h2 v-if="winner" >Winner: {{ winner }}</h2>
  <h2 v-else>Player Move: {{ turn }}</h2>
  <div id="board">
    <div class="square" v-for="(square, index) in squares" :key="square" @click="takeTurn(index)">
      {{ square }}
    </div>
  </div>
  <button @click="reset">
    Reset
  </button>
</template>

<script>
import { ref, computed } from 'vue'

export default {
  name: 'MainBoard',
  props: {
    msg: String // Vue has prop typechecking by default
  },
  setup () {
    const turn = ref('X') // player first turn
    const squares = ref([])
    for (let i = 0; i <= 8; i++) squares.value.push('') // make a blank board}
    console.log('squares value: ' + squares.value)
    const winner = computed(() => checkForWinner(squares.value.flat())) // check 4 winner everytime board changes
    console.log('computed:' + squares.value.flat())
    function checkForWinner (value) {
      console.log('check4Winner: ' + value)
      // NOTE: in this version we have a
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8], // horizontal wins
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8], // vertical wins
        [0, 4, 8],
        [2, 4, 6] // cross wins
      ]
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i]
        if (
          value[a] &&
          value[a] === value[b] &&
          value[a] === value[c]
        ) {
          console.log('value[a]: ' + value[a])
          return value[a]
        }
      }
      return null
    }
    function checkForTieGame () {
      // make it a boolean
      // let isTie = true
      // check each space on the board
      squares.value.forEach((s) => {
        // if a space is available
        // if (s === null) isTie = false
      })
      // if tie is true, end the game
      // if (isTie === true) gameOver.value = true
    }
    function takeTurn (n) {
      console.log('takeTurn value: ' + n)
      if (winner.value) return
      squares.value[n] = turn.value
      checkForTieGame()
      turn.value = turn.value === 'X' ? 'O' : 'X'
      console.log('player: ' + turn.value)
    }
    function reset () {
      turn.value = 'O'
      console.log(squares.value.length)
      squares.value = squares.value.map((square) => '')
      // for (let i = 0; i <= 8; i++) squares.value.push('') // make a blank board
    }
    return { squares, turn, winner, checkForWinner, takeTurn, reset }
  }
}
</script>

<style scoped>
button{
  margin-top: 40px;
}
#board {
  border: 1px solid darkred;
  height: 400px;
  width: 400px;
  margin:0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.square {
  background: #fff;
  border: 1px solid #999;
  /*float: left;*/
  font-size: 70px;
  font-weight: bold;
  line-height: 34px;
  height: 100px;
  width: 100px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
}
</style>

感谢您的宝贵时间

将 v-for 循环中的键设置为唯一(索引),因此将 :key="square" 替换为 :key="index"

const { ref, computed } = Vue

const app = Vue.createApp({
  props: {
    msg: String // Vue has prop typechecking by default
  },
  setup () {
    const turn = ref('X') // player first turn
    const squares = ref([])
    for (let i = 0; i <= 8; i++) squares.value.push('') // make a blank board}
    console.log('squares value: ' + squares.value)
    const winner = computed(() => checkForWinner(squares.value.flat())) // check 4 winner everytime board changes
    console.log('computed:' + squares.value.flat())
    function checkForWinner (value) {
      console.log('check4Winner: ' + value)
      // NOTE: in this version we have a
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8], // horizontal wins
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8], // vertical wins
        [0, 4, 8],
        [2, 4, 6] // cross wins
      ]
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i]
        if (
          value[a] &&
          value[a] === value[b] &&
          value[a] === value[c]
        ) {
          console.log('value[a]: ' + value[a])
          return value[a]
        }
      }
      return null
    }
    function checkForTieGame () {
      // make it a boolean
      // let isTie = true
      // check each space on the board
      squares.value.forEach((s) => {
        // if a space is available
        // if (s === null) isTie = false
      })
      // if tie is true, end the game
      // if (isTie === true) gameOver.value = true
    }
    function takeTurn (n) {
      console.log('takeTurn value: ' + n)
      if (winner.value) return
      squares.value[n] = turn.value
      checkForTieGame()
      turn.value = turn.value === 'X' ? 'O' : 'X'
      console.log('player: ' + turn.value)
    }
    function reset () {
      turn.value = 'O'
      squares.value = Array(9).fill('');  
    }
    return { squares, turn, winner, checkForWinner, takeTurn, reset }
  }
})
app.mount('#demo')
button{
  margin-top: 40px;
}
#board {
  border: 1px solid darkred;
  height: 200px;
  width: 200px;
  margin:0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.square {
  background: #fff;
  border: 1px solid #999;
  /*float: left;*/
  font-size: 70px;
  font-weight: bold;
  line-height: 34px;
  height: 50px;
  width: 50px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
}
<script src="https://unpkg.com/vue@3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
  <h2>{{ msg }}</h2>
  <h2 v-if="winner" >Winner: {{ winner }}</h2>
  <h2 v-else>Player Move: {{ turn }}</h2>
  <div id="board">
                                                             <!--  index is unique -->
    <div class="square" v-for="(square, index) in squares" :key="index" @click="takeTurn(index)">
      {{ square }}
    </div>
  </div>
  <button @click="reset">
    Reset
  </button>
</div>