본문 바로가기

Vue/vue 기초 공부하기

Vue로 틱택토만들기(Vuex)

https://github.com/loy124/Vue/tree/master/틱택토Vuex

 

loy124/Vue

Vue 공부. Contribute to loy124/Vue development by creating an account on GitHub.

github.com

 

store을 활용해서 데이터를 모두 vuex store에 저장하고 

root파트인 TicTacToe.vue에서 해당 데이터를 불러오는 방식이다

 

store.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);
//Vue.use 를 사용할때마다 $axios등 기능들이 추가되는 방식
//mutation의 이름들을 변수로 빼고 export 한다
//오타확률을 줄이기위해 이렇게 변수로 빼둔것
export const SET_WINNER = 'SET_WINNER';
export const CLICK_CELL = 'CLICK_CELL';
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';
export const NO_WINNER = 'NO_WINNER';

//export default는 아무렇게 이름을 통해 가져올수 있으나 import store from './store
//export const 는 import {SET_WINNER} from ./store 등 중괄호를 사용해서 원래 이름을 통해서만 불러올 수 있다.
export default new Vuex.Store({
    state: {
        tableData: [
            ['', '', ''],
            ['', '', ''],
            ['', '', ''],
        ],
        turn: 'O',
        winner: '',
    }, //vue의 data와 유사
    getters: {
        // vue의 computed와 유사
        // turnMessage(state) {
        //     return state.turn + '님이 승리하셨습니다.';
        // },
    },
    mutations: {
        //대문자로 정하는게 Vue 커뮤니티의 규칙
        [SET_WINNER](state, winner) {
            state.winner = winner;
        },
        //   SET_WINNER(state, winner) {
        //     state.winner = winner;
        // },
        [CLICK_CELL](state, { row, cell }) {
            // state.tableData[row][cell] = state.turn;
            //Vuex는 this.$set이 없다
            Vue.set(state.tableData[row], cell, state.turn);
        },
        [CHANGE_TURN](state) {
            state.turn = state.turn === 'O' ? 'X' : 'O';
        },
        [RESET_GAME](state) {
            state.turn = 'O';
            state.tableData = [
                ['', '', ''],
                ['', '', ''],
                ['', '', ''],
            ];
        },
        [NO_WINNER](state) {
            state.winner = '';
        },
    }, // state를 수정할 때 사용 (동기적으로)

    actions: {}, // 비동기를 사용할때, 또는 여러 mutation을 연달아 실행할때
});

 

TicTacToe.vue

 

<template>
  <div>
    <div>{{ turn }}님의 턴입니다.</div>
    <!-- <table-component></table-component> -->
    <table>
      <tr
        v-for="(rowData, rowIndex) in tableData"
        :key="rowIndex"
        :rowData="rowData"
      >
        <td
          v-for="(cellData, cellIndex) in rowData"
          @click="onClickTd(rowIndex, cellIndex, cellData)"
          :key="cellIndex"
        >
          {{ cellData }}
        </td>
      </tr>
    </table>

    <div v-if="winner">{{ winner }}님의 승리!</div>
  </div>
</template>

<script>
import { mapState } from 'vuex';
import store, {
  RESET_GAME,
  SET_WINNER,
  CHANGE_TURN,
  CLICK_CELL,
  NO_WINNER,
} from './store';

// import TableComponent from './TableComponent';

export default {
  store,
  data() {
    return {
      data: 1,
    };
  },

  computed: {
    //화살표 함수는 this 사용불가 일반함숨나 this 사용가능
    // winner: {
    //   get: function() {
    //     return this.$store.state.winner;
    //   },
    //   set: function() {},
    // },
    ...mapState(['winner', 'turn', 'tableData']),
    // ...mapState([{
    //   winner : state => state.winner,
    //   turnState: 'turn',
    //   winner(state){
    //     return state.winner + this.data;
    //   }
    // }]),
    // winner() {
    //   return this.$store.state.winner;
    // },
    // turn() {
    //   return this.$store.state.turn;
    // },
  },
  methods: {
    onClickTd(rowIndex, cellIndex, cellData) {
      //this.$set으로 일치화해주는 작업을 실시한다
      // 또한 인덱스를 여러번 쓰는경우 마지막 index를 key로 하면 된다
      if (cellData) return;
      //이벤트버스의 $on(this.onclickTd)
      // this.$set(this.tableData[rowIndex], cellIndex, this.turn);

      //뮤테이션을 부를때 commit을 사용한다
      //오타 방지를 위한 변수명 호출
      this.$store.commit(CLICK_CELL, {
        row: rowIndex,
        cell: cellIndex,
      });

      let win = false;
      if (
        this.tableData[rowIndex][0] === this.turn &&
        this.tableData[rowIndex][1] === this.turn &&
        this.tableData[rowIndex][2] === this.turn
      ) {
        win = true;
      }
      if (
        this.tableData[0][cellIndex] === this.turn &&
        this.tableData[1][cellIndex] === this.turn &&
        this.tableData[2][cellIndex] === this.turn
      ) {
        win = true;
      }

      //대각선
      if (
        this.tableData[0][0] === this.turn &&
        this.tableData[1][1] === this.turn &&
        this.tableData[2][2] === this.turn
      ) {
        win = true;
      }
      if (
        this.tableData[0][2] === this.turn &&
        this.tableData[1][1] === this.turn &&
        this.tableData[2][0] === this.turn
      ) {
        win = true;
      }
      //승리했을때
      if (win) {
        this.winner = this.turn;
        this.$store.commit(SET_WINNER, this.turn);
        this.$store.commit(RESET_GAME);
      } else {
        //무승부일때
        let all = true; //all이 true면 무승부
        //무승부 검사
        this.tableData.forEach(row => {
          //2차원 배열이니 두번 forEach 실행
          row.forEach(cell => {
            // 칸이 비어있는경우면 all = false
            if (!cell) {
              all = false;
            }
          });
        });
        //무승부일때 값 초기화
        if (all) {
          this.$store.commit(NO_WINNER);
          this.turn = 'O';
          this.$store.commit(RESET_GAME);
          //무승부가 아니므로 턴만 넘긴다
        } else {
          // this.turn = this.turn === 'O' ? 'X' : 'O';
          this.$store.commit(CHANGE_TURN);
        }
      }
    },
  },
};
</script>

<style>
table {
  border-collapse: collapse;
}
td {
  border: 1px solid black;
  width: 40px;
  height: 40px;
  text-align: center;
}
</style>

 

 

슬롯 활용해보기 

TicTacto.vue

tableComponet를 넣었다.

 

<template>
  <div>
    <div>{{ turn }}님의 턴입니다.</div>
    <!-- <table-component></table-component> -->
    <table-component>
      <!-- 슬롯 활용의 장점: 부모 컴포넌트에 함수들을 다 모아두었는데
       해당 함수들을 사용함과 동시에 렌더링은 TableComponent에서 되게 하는 장점-->
      <tr
        v-for="(rowData, rowIndex) in tableData"
        :key="rowIndex"
        :rowData="rowData"
      >
        <td
          v-for="(cellData, cellIndex) in rowData"
          @click="onClickTd(rowIndex, cellIndex, cellData)"
          :key="cellIndex"
        >
          {{ cellData }}
        </td>
      </tr>
    </table-component>

    <div v-if="winner">{{ winner }}님의 승리!</div>
  </div>
</template>

<script>
import { mapState } from 'vuex';
import TableComponent from './TableComponent';
import store, {
  RESET_GAME,
  SET_WINNER,
  CHANGE_TURN,
  CLICK_CELL,
  NO_WINNER,
} from './store';

// import TableComponent from './TableComponent';

export default {
  store,
  components: {
    TableComponent,
  },
  data() {
    return {
      data: 1,
    };
  },

  computed: {
    //화살표 함수는 this 사용불가 일반함숨나 this 사용가능
    // winner: {
    //   get: function() {
    //     return this.$store.state.winner;
    //   },
    //   set: function() {},
    // },
    ...mapState(['winner', 'turn', 'tableData']),
    // ...mapState([{
    //   winner : state => state.winner,
    //   turnState: 'turn',
    //   winner(state){
    //     return state.winner + this.data;
    //   }
    // }]),
    // winner() {
    //   return this.$store.state.winner;
    // },
    // turn() {
    //   return this.$store.state.turn;
    // },
  },
  methods: {
    onClickTd(rowIndex, cellIndex, cellData) {
      //this.$set으로 일치화해주는 작업을 실시한다
      // 또한 인덱스를 여러번 쓰는경우 마지막 index를 key로 하면 된다
      if (cellData) return;
      //이벤트버스의 $on(this.onclickTd)
      // this.$set(this.tableData[rowIndex], cellIndex, this.turn);

      //뮤테이션을 부를때 commit을 사용한다
      //오타 방지를 위한 변수명 호출
      this.$store.commit(CLICK_CELL, {
        row: rowIndex,
        cell: cellIndex,
      });

      let win = false;
      if (
        this.tableData[rowIndex][0] === this.turn &&
        this.tableData[rowIndex][1] === this.turn &&
        this.tableData[rowIndex][2] === this.turn
      ) {
        win = true;
      }
      if (
        this.tableData[0][cellIndex] === this.turn &&
        this.tableData[1][cellIndex] === this.turn &&
        this.tableData[2][cellIndex] === this.turn
      ) {
        win = true;
      }

      //대각선
      if (
        this.tableData[0][0] === this.turn &&
        this.tableData[1][1] === this.turn &&
        this.tableData[2][2] === this.turn
      ) {
        win = true;
      }
      if (
        this.tableData[0][2] === this.turn &&
        this.tableData[1][1] === this.turn &&
        this.tableData[2][0] === this.turn
      ) {
        win = true;
      }
      //승리했을때
      if (win) {
        this.winner = this.turn;
        this.$store.commit(SET_WINNER, this.turn);
        this.$store.commit(RESET_GAME);
      } else {
        //무승부일때
        let all = true; //all이 true면 무승부
        //무승부 검사
        this.tableData.forEach(row => {
          //2차원 배열이니 두번 forEach 실행
          row.forEach(cell => {
            // 칸이 비어있는경우면 all = false
            if (!cell) {
              all = false;
            }
          });
        });
        //무승부일때 값 초기화
        if (all) {
          this.$store.commit(NO_WINNER);
          this.turn = 'O';
          this.$store.commit(RESET_GAME);
          //무승부가 아니므로 턴만 넘긴다
        } else {
          // this.turn = this.turn === 'O' ? 'X' : 'O';
          this.$store.commit(CHANGE_TURN);
        }
      }
    },
  },
};
</script>

<style>
table {
  border-collapse: collapse;
}
td {
  border: 1px solid black;
  width: 40px;
  height: 40px;
  text-align: center;
}
</style>

 

슬롯 활용의 장점: 부모 컴포넌트에 함수들을 다 모아두었는데

       해당 함수들을 사용함(데이터를 한곳에서 관리한다

 

 

TableComponet.vue

 

<template>
  <table>
    <slot>
      <!-- 테이블 컴포넌트에 아무값도 안들어갔으면 슬롯 안에있는 기본값이 들어간다 -->
      <tr>
        <td></td>
      </tr>
    </slot>
  </table>
</template>

<script>
export default {};
</script>