본문 바로가기
학습/Vue.js

웹 게임을 만들며 배우는 Vue(섹션별 회고)

코동이 2024. 8. 1.

*개요

 

https://www.inflearn.com/course/web-game-vue

 

 

웹 게임을 만들며 배우는 Vue 강의를 듣고 유용한 문법을 섹션별로 회고합니다.

 

데이터 중심으로 사고하라(화면에서 변경되는 부분과 아닌 부분을 잘 구분하라)

 

 

섹션 1 끝말잇기

- ref를 사용하면 특정 요소에 접근할 수 있습니다. 예를 들어, this.$refs를 사용해 ref="answer"로 정의한 요소에 계속 커서를 유지하도록 focus()를 걸 수 있습니다. 

<template>
	<form @submit="onSubmit">
		<input type="text" ref="answer" v-model="value" />
		<button>제출</button>
	</form>
</template>
...
export default {
	...
    methods : {
    	onSubmit(e) {
            e.preventDefault();
            this.$refs.answer.focus();
        }
    }
}

 

 

 

섹션 2 구구단

- 같은 화면을 여러번 넣고 싶다면(각기 다른 data를 유지) 컴포넌트를 활용합니다.

...
<div id="root">
    <word-reply start-word="초밥"></word-reply>
    <word-reply start-word="박수"></word-reply>
    <word-reply start-word="컴퓨터"></word-reply>
</div>
...

 

 끝말잇기 여러 개를 동시에 실행하는 경우, 각 끝말잇기 입력값을 고유한 data에 저장하기 위해서 컴포넌트를 활용합니다. 그렇지 않다면 keyword1, keyword2, keyword3처럼 끝말잇기 게임을 위한 data 변수와 메서드 및 코드를 중복 생성해야 합니다.

 

- props를 사용할 때 상위 컴포넌트에서 v-bind를 사용하지 않습니다.

<word-reply start-word="초밥"></word-reply> //옳은 문법
<word-reply v-bind:start-word="초밥"></word-reply> // 잘못된 문법

 

 

섹션 3 숫자야구

- npm i webpack webpack-cli -D

npm을 이용해 webpack을 설치할 수 있습니다. (-D는 개발용을 의미)

 

- webpack.config.js에서 통상 entry, module, plugins, output 4개를 정의합니다.

 

- entry는 어플리케이션 구조, module은 js이외에 관리할 확장자, plugins는 라이브러리, output은 webpack 빌드 결과를 정의합니다.

 

- splice를 활용해 랜덤한 숫자를 뽑을 수 있습니다.

const candidate = [1,2,3,4,5,6,7,8,9];
for (let i = 0; i < 4; i++) {
	let chosen = candidate.splice(Math.floor(Math.random() * (9 - i)), 1)[0];
}

- Math.random()0~1 사이 난수이며 Math.floor버림입니다.

ex) Math.floor(Math.random() * 9) 가능한 정수: 0~8

 

- splice 함수를 사용해 배열을 자를 수 있습니다.

ex) candidate.splice(6, 1): [7]

 

- candidate.splice(6,1)[0]하면 배열 값을 정수로 뽑을 수 있습니다: 7

 

 

 

섹션 4 반응속도

- package.json의 scripts에 자동으로 코드 변경을 감지해서 반영하도록 할 수 있습니다.

"scripts" : {
    "build" : "webpack --watch",
    "dev" : "webpack-dev-server --hot"
}

 

npm run build, npm run dev로 실행할 수 있으며 전자는 서버가 없고 후자는 서버가 존재합니다.

 

- v-bind를 class에 적용하여 다양한 css를 적용할 수 있습니다.

<template>
  <div id="screen" :class="state"></div>
</template>
...
export default {
  data() {
    return {
      state: "",
    }
  }
}
...
<script scoped>
  #screen.waiting: black;
  #screen.now: red;
</script>

 

:class="state" 에서 state는 data에 선언된 변수입니다. state가 변함에 따라서 css를 다르게 적용 할 수 있습니다.

 

 

- computed를 사용하면 data에 의해 template이 변경되는 것을 예방할 수 있습니다. 반대로, computed를 사용하지 않으면 data가 변경될 때마다 template를 다시 렌더링해야 합니다.

<template>
<div>{{ message }}</div>
//<div>{{ tries.splice((a,c) -> a + c, 0) / this.result.length || 0 }}</div>
<div>{{ average }}</div>
</template>

export default {
  data() {
    message: 'hello',
    tries: []
  },
  computed: {
    average() {
      return return this.result.reduce((a, c) => a + c, 0) / this.result.length || 0;
    }
  }
}

 

 만약 average를 사용하지 않고 template 내에서 tries 계산을 했으면, message가 변경될 때 tries를 또 계산해야 합니다. 하지만, computed에서 계산한 average를 활용하면 캐싱 효과가 있어서 값을 저장하고 가져다가 쓰면 됩니다. data를 그대로 사용하지 않고 가공이 필요한 경우 computed에 정의해 사용하면 캐싱에 도움이 됩니다.

 

- || 연산자는 null, undefined, NaN, false, 0, 빈 문자열을 체크해서 디폴트 값을 정할 수 있습니다.

 

- v-showdisplay: none으로 화면에만 보이지 않도록 설정이 가능하며, v-if는 해당하지 않는 경우 화면에 그리지 않고 아예 존재 자체를 제거합니다.

 

- template을 사용하면 여러개의 요소를 하나의 빈 껍데기로 씌울 수 있습니다.

<template> // 아무런 의미가 없는 코드
  <div>1</div>
  <div>2</div>
</template> // 아무런 의미가 없는 코드

 

- css에도 v-bind를 사용하여 data에 따라 다양하게 적용이 가능합니다. :class="state" 를 선언하면, class 속성에 바인딩되는 값에 따라서 다른 css가 적용됩니다.

<template>
  <div id="screen" :class="state" @click="onClickScreen">{{message}}</div>
</template>
...
export default {
  data() {
    return {
      state: 'waiting'
    }
  },
  methods: {
  	onClickScreen() {
      if (this.state == 'waiting') {
      	this.state = 'ready';
        ...
      }
    }
  },
  <style scoped>
    #screen {
       width: 300px;
       height: 200px;
       text-align: center;
       user-select: none;
     }
    #screen.waiting {
      background-color: aqua;
    }
    #screen.ready {
      background-color: red;
      color: white;
    }
    #screen.now {
      background-color: greenyellow;
    }
  </style>
}

 

 

섹션 5 가위바위보

- mounted()는 화면에 DOM이 모두 그려지고 동작할 코드를 정의합니다. 화면이 모두 렌더링되고, 가위바위보가 나와야 하므로 coumpted() 가 아닌 mounted()에 정의합니다.

 

- :class="state"와 마찬가지로 :style="computedStyleObject"로 css를 관리 할 수 있습니다. 섹션 4의 state의 경우, data()에 정의되어 [:class="data 변수"] -> class="waiting" 으로 사용되었으며, 섹션 5의 computedStyleObject의 경우, computed()에 정의되어  [:style="computed 변수"] -> style="background': 'url(...) 0 0" 으로 사용됩니다. 해당 변수에 맞는 실제 값이 대체됩니다.

 

- 백틱(`) 안에서 data에 있는 imgCoord는 ${this.imgCoord}로 표현합니다.

 

<template>
  <div id="computer" :style="computedStyleObject"></div>
</template>

<script>
let interval = null;
let timeout = null;
const rspCoords = {
  바위: '0',
  가위: '-142px',
  보 : '-284px',
}
export default {
  data() {
  	return {
    	imgCoord: rspCoords.바위,
    }
  },
  computed: {
    computedStyleObject() {
     return background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${this.imgCoord} 0`,
    }
  },
  methods: {
    changeHand() {
      interval = setInterval(() => {
        if (this.imgCoord === rspCoords.바위) {
          this.imgCoord = rspCoords.가위;
        } else if (this.imgCoord === rspCoords.가위) {
          this.imgCoord = rspCoords.보;
        } else if (this.imgCoord === rspCoords.보) {
          this.imgCoord = rspCoords.바위;
        }
      }, 100);
      
      ...
      
      timeout = setTimeout(() => {
        this.changeHand();
      }, 1000);
    },
  },
  mounted() {
    this.changeHand();
  },
  beforeDestroy() {
  	clearInterval(interval);
    clearTimeout(timeout);
  }
}
</script>

 

 setTimeout은 기본적으로 1번만 실행하며, 중첩 setTimeout으로 무한 실행할 수 있습니다. 정의한 함수 실행이 다 끝나고 대기를 시작합니다. setInterval은 기본적으로 무한정 실행하며, 함수시작과 동시에 대기시간이 시작됩니다.

 

 

섹션 6 로또 추첨기

- 로또를 화면에 그리는 작업은 섹션 5와 마찬가지로 DOM이 그려진 이후에 작동하므로, mounted()에 정의합니다.

mounted() {
  this.showBalls();
}

 

 

- 부모 컴포넌트와 자식 컴포넌트는 props로 data를 주고 받을 수 있습니다. [:자식 컴포넌트props 속성 = "부모컴포넌트 data 변수"] => :number="ball"

 

//LottoGenerator.vue
<lotto-ball :v-for="ball in winBalls" :key="ball" :number="ball"></lotto-ball>
<lotto-ball :number="bonus"></lotto-ball>

data() {
  return {
    winBalls: [],
    bonus: null,
  }
}

//LottoBall.vue
props: {
  number: Number
}

 

- watch를 사용하면 data의 변경을  비동기로 감지할 수 있지만, 많이 사용하는경우 예상치 못한 부작용이 발생할 수 있기 때문에 가급적 사용하지 않습니다.(watch에 선언한 data가 서로 영향을 주고받는 경우 무한 루프에 빠질 수 있습니다.)

 

- setTimeout()은 beforeDestroy에서 꼭 해제하여 메모리 누수를 예방한다.

beforeDestroy() {
  timeout.forEach(t => {
  	clearTimeout(t);
  });
}

 

 

섹션 7 틱택토

- vue의 배열에 값을 직접 할당하면 template에서 변화가 감지되지 않습니다. (관련 문서: 배열 변경 감지)

따라서 this.$set(), Vue.set()을 사용해야합니다.

this.$set(rootData.tableData[this.rowIndex], this.cellIndex, rootData.turn); // 클릭한 셀

 

 

- 자식 컴포넌트에서 상위 컴포넌트의 data를 호출 할 수 있습니다. this.$root.$data를 사용하면, 최상위 부모 컴포넌트의 data를 조회할 수 있으며, 바로 상위 부모 컴포넌트는 this.$parent.$data를 사용합니다.

 

- EventBus를 사용하여 부모-자식 컴포넌트 관계에 상관없이 컴포넌트간에 이벤트를 발생시킬 수 있습니다. EventBus를 사용하기 위해 비어있는 파일을 만든 후, 이벤트 등록을 위해 EventBus.$on('이벤트명',함수명), 이벤트 사용을 위해  EventBus.$emit('이벤트명', 매개변수)을 정의합니다. 보통 이벤트 등록은 created()에서 하며, 이벤트 해제는 beforeDestory()에서 합니다.

 

//EventBus.js
import Vue from 'vue';

export default new Vue();


//TicTacToe.vue
<script>
import EventBus from './EventBus';

export default {
  methods: {
    onClickTd() {
      ...
    }
  },
  created() {
    EventBus.$on('clickTd', this.onClickTd);
  }
}
</script>

//TdComponent.vue
<script>
import EventBus from './EventBus';

export default {
  props: {
    rowIndex: Number,
    cellIndex: Number
  },
  methods: {
    onClickTd() {
      EventBus.$emit('clickTd;, this.rowIndex, this.cellIndex);
    }
  }
}
</script>

 

- store(Vuex)에서 관리하는 데이터는 state에 정의합니다. 만약 state 데이터들이 여러 컴포넌트에서 사용된다면 각 컴포넌트는 store에서 관리하도록 합니다. 각 컴포넌트에서 공유 state를 사용하지 않으면, 데이터 동기화가 되지 않아 원하는 화면이 그려지지 않을 수 있습니다.

//store.js
const state: {
   turn: '',
   winner: 'O',
}

//TicTacToe.vue
export default {
  //Vuex에서 데이터를 관리하므로 mapState 사용
  computed() {
  	...mapState(['turn', 'winner'])
  }
  
  //data에 정의해서 사용하면 Vuex와 동기화가 안될 수도 있다.
  data() {
    return {
      turn: '',
      winner: 'O',
    }
  }
}

 

반응형