vue (demo-1)

做一个井字棋


思路

  • 真的不会,怎么办
  • 三行不会,先做一行
  • 一行不会,先做一个,一个框可XO
  • 也不会,那先做一个框点一下出现X

动手做

  • 第一步,实现刷新页面等概率出现XO
<template>
  <div class="new">
    <div v-if="Math.random() > 0.5">X</div>
    <div v-else>O</div>
  </div>
</template>
<script></script>
<style></style>

这里我们学习到条件语句

<template>
  <div class="new">
    <div v-if="true">X</div>
    <div v-else>O</div>  //结果页面显示 X

    <div v-if="false">X</div>
    <div v-else>O</div>  //结果页面显示 O
  </div>
</template>
  • data 的使用
<template>
  <div class="new">
    <div v-if="a">X</div>
    <div v-else>空</div>
  </div>
</template>

<script>
  export default {
    data() {  //ES6 语法,表示一个函数
      return { a: false }
    }
  }
</script>

页面默认没有显示内容

  • 接下来监听事件
<template>
  <div class="new">
    <div v-if="a">X</div>
    <div v-else v-on:click="a = true">空</div>
  </div>
</template>

页面为空,点击后为 X

  • 优化下代码

打开控制台,检查元素,发现X<div>X</div>包裹,如何去掉多余的<div></div>

<template>
  <div class="new">
    <template v-if="a">X</template>
    <template v-else v-on:click="a = true">空</template>
  </div>
</template>

出现 bug 了,元素被隐藏,绑定的事件就无效了,所以应该这样绑定事件

<template>
  <div class="new"  v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else>空</template>
  </div>
</template>
  • 实现一个空白格子点击后出现 X
<template>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else></template>
  </div>
</template>
<script>
  export default {
    data() {  //ES6 语法,表示一个函数
      return { a: false }
    }
  }
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>
  • 一个格子变成一行格子
<template>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else></template>
  </div>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else></template>
  </div>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else></template>
  </div>
</template>

报错,原因:vuetemplate里面只能有一个根元素,就是只能有一个div,所以要用一个div把它们包起来

<template>
  <div>
    <div class="cell" v-on:click="a = true">
      <template v-if="a">X</template>
      <template v-else></template>
    </div>
    <div class="cell" v-on:click="a = true">
      <template v-if="a">X</template>
      <template v-else></template>
    </div>
    <div class="cell" v-on:click="a = true">
      <template v-if="a">X</template>
      <template v-else></template>
    </div>
  </div>
</template>

又出现 bug 了,点击一个,三个都变成X了,因为点击后都是"a = true",解决方法,给每个编号(傻瓜办法),这时候我们想到用组件

  • 如何创建一个组件

App.vue 同目录下,创建 Cell.vue

Cell.vue

<template>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">X</template>
    <template v-else></template>
  </div>
</template>
<script>
  export default {
    data() { 
      return { a: false }
    }
  }
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>
App.vue

<template>
  <div>
    <Cell />
    <Cell />
    <Cell />
  </div>
</template>
<script>
  import Cell from "./Cell.vue"  // .vue 后缀可以省略
  export default {
    components: { Cell }
  }
</script>
<style></style>
  • 实现九个格子

实现空白棋盘,点击后出现X

App.vue

<template>
  <div>
    <div class="row">
      <Cell />
      <Cell />
      <Cell />
    </div>
    <div class="row">
      <Cell />
      <Cell />
      <Cell />
    </div>
    <div class="row">
      <Cell />
      <Cell />
      <Cell />
    </div>
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>
  • 点击后的内容随text: "值"改变而改变
Cell.vue

<template>
  <div class="cell" v-on:click="a = true">
    <template v-if="a">{{text}}</template>  //---
    <template v-else></template>
  </div>
</template>
<script>
  export default {
    data() { 
      return { a: false, text: "O" }  //---
    }
  }
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

但是怎么实现用户的点击次数不同而改变的,这是这个项目的最复杂的地方

  • 侦听事件
App.vue

<template>
  <div>
    <div class="row">
      <Cell v-on:click="onClickCell" />  //---
      <Cell v-on:click="onClickCell" />
      <Cell v-on:click="onClickCell" />
    </div>
    <div class="row">
      <Cell v-on:click="onClickCell" />
      <Cell v-on:click="onClickCell" />
      <Cell v-on:click="onClickCell" />
    </div>
    <div class="row">
      <Cell v-on:click="onClickCell" />
      <Cell v-on:click="onClickCell" />
      <Cell v-on:click="onClickCell" />
    </div>
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    methods: {  //---
      onClickCell() {
        console.log('某个 cell 被点击了')
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>

直接这样写是没有用的,需要在 Cell.vue 里面手动的通知一下

Cell.vue

<template>
  <div class="cell" v-on:click="onClickSelf">
    <template v-if="a">{{text}}</template>  //---
    <template v-else></template>
  </div>
</template>
<script>
  export default {
    data() { 
      return { a: false, text: "O" } 
    },
    methods: {  //---
      onClickSelf() {
        this.a = true;
        this.$emit('click');
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

监听事件:在外面写v-on:click="",在里面写this.$emit('click'),这是相对应的

  • 当前被点的是第几次
  1. 写个div显示在头部,表示这是第几次被点击
  2. n通知给所有 Cell
App.vue

<template>
  <div>
     <div>n 的值为 {{n}}</div>  //---
    <div class="row">
      <Cell v-on:click="onClickCell" v-bind:n="n" />   //---
      <Cell v-on:click="onClickCell" v-bind:n="n" />
      <Cell v-on:click="onClickCell" v-bind:n="n" />
    </div>
    <div class="row">
      <Cell v-on:click="onClickCell" v-bind:n="n" />
      <Cell v-on:click="onClickCell" v-bind:n="n" />
      <Cell v-on:click="onClickCell" v-bind:n="n" />
    </div>
    <div class="row">
      <Cell v-on:click="onClickCell" v-bind:n="n" />
      <Cell v-on:click="onClickCell" v-bind:n="n" />
      <Cell v-on:click="onClickCell" v-bind:n="n" />
    </div>
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    data() {
      return { n: 0 }  //---
    }
    methods: { 
      onClickCell() {
        console.log('某个 cell 被点击了')
        this.n = this.n + 1;  //---
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>

如何让组件接收到外面的n

Cell.vue

<template>
  <div>
    {{n}}  //---
    <div class="cell" v-on:click="onClickSelf">
      <template v-if="a">{{text}}</template>  
      <template v-else></template>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["n"],  //---
    data() {  
      return { a: false, text: "O" } 
    },
    methods: {  
      onClickSelf() {
        this.a = true;
        this.$emit('click');
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

接收数据:在外面写v-bind:n="n",在里面写props: ["n"],这是相对应的

  • 一些简写

v-on:click="onClickCell" ==> @click="onClickCell"
v-bind:n="n" ==> :n="n"

  • 通过计算得点击后的结果
Cell.vue

<template>
  <div>
    {{n}}  //---
    <div class="cell" v-on:click="onClickSelf">
      <template v-if="a">{{text}}</template>  
      <template v-else></template>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["n"],  //---
    data() {  
      return { a: false }   //---
    },
    conputed: {  //计算属性
      text() {
        return this.n % 2 == 0 ? "X" : "O";
      }
    }
    methods: {  
      onClickSelf() {
        this.a = true;
        this.$emit('click');
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

出现 bug 第一次点击为O,第二次点击为X,但是连同之前的都变为X,第三次点击都变为O...

原因:每次n变化text(){}也跟着重新变化

所以不能用计算属性,返回之前的做法,在点击的时候确定

  • 返回之前的做法
Cell.vue

<template>
  <div>
    {{n}}  
    <div class="cell" v-on:click="onClickSelf">
      <template v-if="a">{{text}}</template>  
      <template v-else></template>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["n"], 
    data() {  
      return { a: false, text: "" } ;  //---
    },
    methods: {  
      onClickSelf() {
        this.a = true;
        this.text = this.n % 2 === 0 ? "X" : "O";  //---
        this.$emit('click');
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

又出现 bug 了,可以重复点击

  • 解决重复点击发生改变的问题
Cell.vue

<template>
  <div>
    {{n}}  
    <div class="cell" v-on:click="onClickSelf">
      <template v-if="a">{{text}}</template>  
      <template v-else></template>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["n"], 
    data() {  
      return { a: false, text: "" } ;
    },
    methods: {  
      onClickSelf() {
        if (this.text === "") {  //---
          return;
        }
        this.a = true;
        this.text = this.n % 2 === 0 ? "X" : "O";  
        this.$emit('click');
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

加个判断语句就可以解决了

  • 给点击事件编号
App.vue

<template>
  <div>
     <div>n 的值为 {{n}}</div>  //---
    <div class="row">
      <Cell @click="onClickCell(0)" :n="n" />   //---
      <Cell @click="onClickCell(1)" :n="n" />
      <Cell @click="onClickCell(2)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(3)" :n="n" />
      <Cell @click="onClickCell(4)" :n="n" />
      <Cell @click="onClickCell(5)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(6)" :n="n" />
      <Cell @click="onClickCell(7)" :n="n" />
      <Cell @click="onClickCell(8)" :n="n" />
    </div>
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    data() {
      return { n: 0 } 
    }
    methods: { 
      onClickCell(i) {  //---
        console.log(`${i}号被点击`)  //---
        this.n = this.n + 1;  
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>

正确的写法应该是写个函数<Cell @click="() => onClickCell(0)" :n="n" />,但是vue做了个语法糖,如果发现你直接调用一个函数,它会等click的时候再去调用,不会直接调用

点击后console.log几号被点击,所以我们知道哪个被点击了

但是我们不知道点击的是X还是O

  • 怎么知道点击的是X还是O
Cell.vue

<template>
  <div>
    {{n}}  
    <div class="cell" v-on:click="onClickSelf">
      <template v-if="a">{{text}}</template>  
      <template v-else></template>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["n"], 
    data() {  
      return { a: false, text: "" } ;
    },
    methods: {  
      onClickSelf() {
        if (this.text === "") { 
          return;
        }
        this.a = true;
        this.text = this.n % 2 === 0 ? "X" : "O";  
        this.$emit('click', this.text);  //给点击事件加一个参数
      }
    }
  };
</script>
<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }
</style>

但是我怎么拿到这个内容呢

App.vue

<template>
  <div>
     <div>n 的值为 {{n}}</div> 
    <div class="row">
      <Cell @click="onClickCell(0, $event)" :n="n" />   //---
      <Cell @click="onClickCell(1, $event)" :n="n" />
      <Cell @click="onClickCell(2, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(3, $event)" :n="n" />
      <Cell @click="onClickCell(4, $event)" :n="n" />
      <Cell @click="onClickCell(5, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(6, $event)" :n="n" />
      <Cell @click="onClickCell(7, $event)" :n="n" />
      <Cell @click="onClickCell(8, $event)" :n="n" />
    </div>
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    data() {
      return { n: 0 } 
    }
    methods: { 
      onClickCell(i, text) {   //---
        console.log(`${i}号被点击,内容是:${text}`)   //---
        this.n = this.n + 1;  
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>

$event的写法就是接收里面$emit的数据

  • 创建一个二维数组保存被点击的结果
App.vue

<template>
  <div>
     <div>n 的值为 {{n}}</div> 
    <div class="row">
      <Cell @click="onClickCell(0, $event)" :n="n" />   //---
      <Cell @click="onClickCell(1, $event)" :n="n" />
      <Cell @click="onClickCell(2, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(3, $event)" :n="n" />
      <Cell @click="onClickCell(4, $event)" :n="n" />
      <Cell @click="onClickCell(5, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(6, $event)" :n="n" />
      <Cell @click="onClickCell(7, $event)" :n="n" />
      <Cell @click="onClickCell(8, $event)" :n="n" />
    </div>
    <div>{{map}}</div>  //打出map ---
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    data() {
      return { 
        n: 0,
        map: [  //---
          [null, null, null],
          [null, null, null], 
          [null, null, null]
        ],
      } 
    }
    methods: { 
      onClickCell(i, text) {  
        console.log(`${i}号被点击,内容是:${text}`) 
        this.map[Math.floor(i / 3)][i % 3] = text;  //结果传入数组 ---
        this.n = this.n + 1;  
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>
  • 判断输赢
App.vue

<template>
  <div>
     <div>n 的值为 {{n}}</div> 
    <div class="row">
      <Cell @click="onClickCell(0, $event)" :n="n" />   //---
      <Cell @click="onClickCell(1, $event)" :n="n" />
      <Cell @click="onClickCell(2, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(3, $event)" :n="n" />
      <Cell @click="onClickCell(4, $event)" :n="n" />
      <Cell @click="onClickCell(5, $event)" :n="n" />
    </div>
    <div class="row">
      <Cell @click="onClickCell(6, $event)" :n="n" />
      <Cell @click="onClickCell(7, $event)" :n="n" />
      <Cell @click="onClickCell(8, $event)" :n="n" />
    </div>
    <div>{{map}}</div> 
  </div>
</template>
<script>
  import Cell from "./Cell.vue" ;
  export default {
    components: { Cell },
    data() {
      return { 
        n: 0,
        map: [  
          [null, null, null],
          [null, null, null], 
          [null, null, null]
        ],
      } 
    }
    methods: { 
      onClickCell(i, text) {  
        console.log(`${i}号被点击,内容是:${text}`) 
        this.map[Math.floor(i / 3)][i % 3] = text; 
        this.n = this.n + 1; 
        this.tell();  //---
      },
      tell() {  //---
        for (let i = 0; i < 3; i++) {
          if (
            this.map[i][0] !== null &&
            this.map[i][0] == this.map[i][1] &&
            this.map[i][1] == this.map[i][2]
          ) {  //行判断
            this.result = this.map[i][0];
          }
        }
        for (let j = 0; j < 3; j++) {
          if (
            this.map[0][j] !== null &&
            this.map[0][j] == this.map[1][j] &&
            this.map[1][j] == this.map[2][j]
          ) {  //列判断
            this.result = this.map[0][j];
          }
        }
        if (
          this.map[0][0] !== null &&
          this.map[0][0] == this.map[1][1] &&
          this.map[1][1] == this.map[2][2]
        ) {  //斜判断
          this.result = this.map[0][0];
        }

        if (
          this.map[0][2] !== null &&
          this.map[0][2] == this.map[1][1] &&
          this.map[1][1] == this.map[2][0]
        ) {  //斜判断
          this.result = this.map[0][2];
        }
      }
    }
  };
</script>
<style>
  .row {
    display: flex;
  }
</style>
  • 样式优化
App.vue

<template>
  <div class="wrapper">
    <div>第{{n}}手</div>
    <div class="chess">
      <div class="row">
        <Cell v-on:click="onClickCell(0, $event)" :n="n" />
        <Cell v-on:click="onClickCell(1, $event)" :n="n" />
        <Cell v-on:click="onClickCell(2, $event)" :n="n" />
      </div>
      <div class="row">
        <Cell v-on:click="onClickCell(3, $event)" :n="n" />
        <Cell v-on:click="onClickCell(4, $event)" :n="n" />
        <Cell v-on:click="onClickCell(5, $event)" :n="n" />
      </div>
      <div class="row">
        <Cell v-on:click="onClickCell(6, $event)" :n="n" />
        <Cell v-on:click="onClickCell(7, $event)" :n="n" />
        <Cell v-on:click="onClickCell(8, $event)" :n="n" />
      </div>
    </div>
    <div>结果:{{result == null ? `胜负未分` : `胜方为${result}`}}</div>
  </div>
</template>

<script>
import Cell from "./Cell";
export default {
  components: { Cell },
  data() {
    return {
      n: 0,
      map: [[null, null, null], [null, null, null], [null, null, null]],
      result: null
    };
  },
  methods: {
    onClickCell(i, text) {
      console.log(`${i}号被点击,内容是:${text}`);
      this.map[Math.floor(i / 3)][i % 3] = text;
      this.n = this.n + 1;
      this.tell();
    },
    tell() {
      for (let i = 0; i < 3; i++) {
        if (
          this.map[i][0] !== null &&
          this.map[i][0] == this.map[i][1] &&
          this.map[i][1] == this.map[i][2]
        ) {
          this.result = this.map[i][0];
        }
      }
      for (let j = 0; j < 3; j++) {
        if (
          this.map[0][j] !== null &&
          this.map[0][j] == this.map[1][j] &&
          this.map[1][j] == this.map[2][j]
        ) {
          this.result = this.map[0][j];
        }
      }
      if (
        this.map[0][0] !== null &&
        this.map[0][0] == this.map[1][1] &&
        this.map[1][1] == this.map[2][2]
      ) {
        this.result = this.map[0][0];
      }

      if (
        this.map[0][2] !== null &&
        this.map[0][2] == this.map[1][1] &&
        this.map[1][1] == this.map[2][0]
      ) {
        this.result = this.map[0][2];
      }
    }
  }
};
</script>

<style>
.row {
  display: flex;
}

.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
</style>
Cell.vue

<template>
  <div class="cell" v-on:click="onClickSelf">
    <template v-if="a">{{text}}</template>
    <template v-else></template>
  </div>
</template>

<script>
export default {
  props: ["n"],
  data() {
    // ES6 语法
    return { a: false, text: ""}
  },
  methods: {
      onClickSelf() {
          if (this.text !== "") {
              return;
          }
          this.a = true;
          this.text = this.n % 2 == 0 ? "x" : "o";
          this.$emit('click', this.text);
          
      }
  }
}
</script>

<style>
  .cell {
    border: 1px solid black;
    width: 100px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 80px;
  }  
</style>
  • 部署到 GitHub 上
  1. 控制台命令行yarn build
  2. 上传到 GitHub 上,预览+/vue-demo-1/index.html报错
    打开控制台,查看Request URL,发现缺少一个目录
    访问+/vue-demo-1/dist/+Ruquest URL地址
  3. 配置路径
    参考资料
复制

module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/my-project/'
    : '/'
}
修改

module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/vue-demo-1/dist/'  //---
    : '/'
}
  1. 重新yarn build,上传,访问https://liujianli000.github.io/vue-demo-1/dist/index.html

预览链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容