[Vue] Composition API - 주사위 만들기 예제

leteu

·

2022. 2. 22. 09:13

 

Composition API 사용법 공유해볼 겸 해서 주사위나 만들어보려고 한다.

.vue 파일 없이 js 파일로만 짜 보려고 한다.

h 함수를 통해 랜더를 할 생각인데 이건 react의 JSX createElement와 유사하게 사용할 수 있다.

jsx로 Vue 컴포넌트 만들다 보면 React도 쉽게 적응하지 않을까 싶다.


( 오류 있다고 나오면 좌측 하단 새로고침 눌러주면 잘 나온다 )

 

#1 뭔가 있어 보이는 초반 세팅

 

 

컴포넌츠 폴더에 Dice 폴더 만들어서 아래 파일들을 생성해준다.

  • index.js
  • Dice.js
  • DiceFace.js
  • dice.sass

 

컴포넌트에 입혀줄 스타일은 sass 파일로 만들어준 뒤 dice를 사용하는 컴포넌트에서 Import 해서 사용하거나 글로벌 sass에서 import 해주는 방식으로 사용할 것이다.

 

index.js에서는 Dice.js에서 짠 JSX 컴포넌트를 불러와서 사용할 것이다. 이렇게 해주면 Dice 폴더를 불러와서 뭔가 있어 보이고 깔끔하게 컴포넌트를 쓸 수 있다.

 

JSX로 코드를 짤 땐 컴포넌트를 적당히 나눠서 사용하면 코드의 재사용성을 늘릴 수 있다.

DiceFace.js에서 눈금을 만들고 Dice.js에서 주사위 컴포넌트로 만들어 주려고 한다.

 

Dice.js 와 DiceFace.js 는 defineComponent를 불러와 내보내 준다.

 

// Dice.js

import { defineComponent } from "vue";
import DiceFace from "./DiceFace";

export default defineComponent({
	components: {
		DiceFace
	},

});

 

// DiceFace.js

import { defineComponent } from "vue";

export default defineComponent({

});

 

 

index.js는 내보내기 한 Dice.js를 불러와서 내보내 준다.

 

// index.js

import Dice from "./Dice";

export default Dice;

 

 

주사위를 확인할 App.vue에서는 Dice 컴포넌트와 dice.sass를 불러와 사용해준다.

 

// App.vue

<template>
	<div class="dice-box">
		<Dice />
	</div>
</template>

<script>
import Dice from '/src/components/Dice';

export default {
	name: "App",
	components: {
		Dice
	},
};
</script>

<style lang="sass">
@import ./components/Dice/dice

.dice-box
	width: 100%
	height: 400px
	display: flex
	justify-content: space-around
	align-items: center
</style>

 

 

 

#2 주사위 눈금 컴포넌트 제작

 

랜더링은 h 함수를 통해 할 수 있습니다.

faceValue를 props로 받아 눈금이 몇인지 받아줍시다.

 

Array.from 함수는 비어있는 배열을 만들어 줄 수 있습니다.

첫 번째 아규먼트로 { length: number } 두 번째 아규먼트로 배열에 값을 리턴해줄 수 있습니다.

 

// DiceFace.js

import { defineComponent, h } from "vue";

export default defineComponent({
  props: {
    faceValue: {
      type: Number,
      validator: val => val >= 1 && val <=6,
      required: true
    }
  },
  setup(props) {
    return () => h('div',
      {
        class: `dice-face dice-face__${props.faceValue}`
      },
      Array.from(
        {
          length: props.faceValue
        },
        (_, index) => index+1
      ).map(dot => {
        return h('div', { class: 'dice-face__dot' }) 
      })
    )
  }
});

 

// Dice.js

import { defineComponent, h } from "vue";
import DiceFace from "./DiceFace";

export default defineComponent({
  setup() {
	return () => [
      h(DiceFace, { faceValue: 1 }),
      h(DiceFace, { faceValue: 2 }),
      h(DiceFace, { faceValue: 3 }),
      h(DiceFace, { faceValue: 4 }),
      h(DiceFace, { faceValue: 5 }),
      h(DiceFace, { faceValue: 6 }),
    ]
  }
});

 

위 코드 작성 시 개발자 도구에서 element가 잘 표출되고 있는 걸 확인할 수 있다.

이제 이쁘게 꾸며보자

 

 

/* dice.sass */

*
	box-sizing: border-box

$dice-size: 100px
$dot-size: 20px

.dice-face
	width: $dice-size
	height: $dice-size
	background-color: #e7e7e7
	object-fit: contain
	border: 1px solid #333
	display: flex
	justify-content: space-between
	&__dot
		display: block
		width: $dot-size
		height: $dot-size
		background-color: #676767
		box-shadow: inset -0.15rem 0.15rem 0.25rem rgba(0, 0, 0, 0.5)
		border-radius: 50%
	&__1
		align-items: center
		justify-content: center
	&__2
		padding: 20px
        > div
		&:last-child
		align-self: flex-end
	&__3
		padding: 15px
		> div
		&:nth-child(2)
			align-self: center
		&:last-child
			align-self: flex-end
	&__4
		padding: 10px
		display: grid
		grid-template-columns: 40px 40px
		align-items: center
		justify-items: center
	&__5
		padding: 12px
		display: grid
		align-items: center
		justify-items: center
		>div
			&:nth-child(1)
				grid-column: 1
			&:nth-child(2)
				grid-column: 3
			&:nth-child(3)
				grid-column: 2
			&:nth-child(4)
				grid-column: 1
			&:nth-child(5)
				grid-column: 3
	&__6
		padding: 10px
		display: grid
		grid-template-columns: 40px 40px
		align-items: center
		justify-items: center

 

 

 

#3 주사위 컴포넌트 제작

 

// Dice.js

import { defineComponent, h } from "vue";
import DiceFace from "./DiceFace";

export default defineComponent({
  props: {
    value: {
      type: Number,
      validator: (val) => val >= 1 && val <= 6,
      required: true
    }
  },
  setup(props) {
    return () =>
      h(
        "div",
        {
          class: `dice dice__show-${props.value}`
        },
        [
          h(DiceFace, { faceValue: 1 }),
          h(DiceFace, { faceValue: 2 }),
          h(DiceFace, { faceValue: 3 }),
          h(DiceFace, { faceValue: 4 }),
          h(DiceFace, { faceValue: 5 }),
          h(DiceFace, { faceValue: 6 })
        ]
      );
  }
});

 

// App.vue

<template>
  <div class="dice-box">
    <Dice :value="1" />
  </div>
</template>

...

 

/* dice.sass */

.dice
	width: $dice-size
	height: $dice-size
	position: relative
	transform-style: preserve-3d

.dice-face
	position: absolute
	top: 0
	left: 0
    
.dice-face
	&__1
		transform: rotateY(0deg) translateZ(50px)
	&__2
		transform: rotateY(90deg) translateZ(50px)
	&__3
		transform: rotateX(-90deg) translateZ(50px)
	&__4
		transform: rotateX(90deg) translateZ(50px)
	&__5
		transform: rotateY(-90deg) translateZ(50px)
	&__6
		transform: rotateY(180deg) translateZ(50px)

...

 

.dice-face에 앱솔루트를 주고 한대 모아준다.

 

.dice는 transform-style를 preserve-3d로 줘서 3d처럼 만들어준다.

 

rotate를 사용해 1번 눈금을 기준으로 정육면체를 만들어준다.

 

주사위는 완성됐다.

 

이제 잘 만들어졌는지 확인해보자.

 

 

#4 keyframes 사용해서 주사위 굴려보기

 

spin 키프레임을 만들어 준 뒤 .roll-dice 클래스에 애니메이션으로 추가해 준다.

 

/* dice.sass */

.roll-dice
  animation: spin .5s linear infinite
  transition: transform 0.8s
  transform-origin: center

@keyframes spin
  0%
    transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg)
  100%
    transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg)

 

// Dice.js

.
.
.
props: {
	.
	.
	.
	roll: {
        type: Boolean,
        default: false
	},
},
setup(props) {
    return () => h(
		"div",
        {
			class: `dice dice__show-${props.value} ${props.roll ? 'roll-dice' : ''}`
        },
        [
            h(DiceFace, { faceValue: 1 }),
            h(DiceFace, { faceValue: 2 }),
            h(DiceFace, { faceValue: 3 }),
            h(DiceFace, { faceValue: 4 }),
            h(DiceFace, { faceValue: 5 }),
            h(DiceFace, { faceValue: 6 })
        ]
    );
}
.
.
.

 

// App.vue

<template>
	<div class="dice-box">
		<Dice :value="1" roll />
	</div>
</template>

 

Dice 컴포넌트에 roll 프롭을 받아서 클래스를 조건 처리해준다.