vue3-ts

0. overview

vue3 composition api 기초 사용법
잘못된 내용이 있을 가능성 100%
자세하고 정확한 내용은 공식문서 확인

가이드

api

* 2. name을 제외한 샘플코드는 <script setup lang="ts"></script>에 작성한 내용임

1. script tag

<!-- options api / setup()을 통한 composition api 사용 -->
<script>
</script>

<!-- lang="ts": typescript 사용(컴포넌트 내 script 태그는 모두 lang 값이 같아야함) -->
<script lang="ts">
</script>

<!-- setup: composition api 사용(sfc에서만 사용 가능) -->
<script setup lang="ts">
</script>

2. name

  • name 속성은 options api로 설정
<script lang="ts">
    export default { name: 'vue3-ts' };
</script>

3. import and use vue functions

import { ref, reactive, computed as com, onMounted as mounted, onUpdated } from 'vue';
const foo = ref('vue3');

// or

import * as vu from 'vue';
const bar = vu.ref('ts');

4. data

4-1. reactive

  • object, array, Map, Set등 non-primitive 타입만 가능
  • 속성을 destructuring하거나 함수의 파라미터로 전달하면 반응성이 없어짐
import { reactive } from 'vue';

const foo = reactive({
    name: 'vue3'
});

const { name } = foo; // 반응형 아님

fun(foo.name);

function fun(name: string): void {
    name = 'ts'; // 반응형 아님
}

4-2. ref(사용 추천)

  • 모든 타입 가능
  • script 에서 .value로 접근해야함
  • template 에서 .value 필요없음
  • 속성을 destructuring하거나 함수의 파라미터로 전달해도 반응성이 살아있음
import { ref } from 'vue';

const foo = ref('vue3');

const bar = ref({
    name: 'ts'
});

// in script
console.log(foo.value);
console.log(bar.value.name);

// in template
// <p>{{ foo }}</p>
// <p>{{ bar.name }}</p>

const { name } = bar.value; // 반응형

fun(bar.value.name);

function fun(name: string): void {
    name = 'js'; // 반응형
}

4-3. with typescript

import { Ref, ref } from 'vue';

const foo: Ref<string> = ref('vue3');

// multi type binding
const foo1: Ref<string | number> = ref('ts'); // string ok
foo1.value = 1;                               // number ok

// or

const bar = ref<string>('vue3');

// multi type binding
const bar1 = ref<string | number>('ts'); // string ok
bar1.value = 1;                          // number ok

5. computed

5-1. get only

import { ref, computed } from 'vue';

const foo = ref('vue3');

const bar = computed((): string => {
    return foo.value === 'vue3' ? 'composition api' : 'options api';
});

5-2. get, set(사용 비추천)

import { ref, computed } from 'vue';

const foo = ref('vue3');

const bar = computed({
    get (): string {
        return foo.value === 'vue3' ? 'composition api' : 'options api';
    },
    set (newValue: string): void {
        foo.value = newValue;
    }
});

6. watch

import { ref, watch } from 'vue';

const foo = ref('vue3');

watch(foo, (newValue: string): void => {
    // your code...
});

// or

watch(foo, (newValue: string, oldValue: string): void => {
    // your code...
});

const bar = ref({
    name: 'ts'
});

// X
 watch(bar.value.name, (newValue: string, oldValue: string): void => {
     // your code...
 });

// O
watch(() => bar.value.name, (newValue: string, oldValue: string): void => {
    // your code...
});

7. refs

  • 템플릿의 ref의 속성과 같은 이름의 반응형 변수를 null로 선언한다.
// <input ref="foo"></input>

import { ref, onMounted } from 'vue';

const foo = ref(null);

onMounted(() => {
    foo.value.focus();
});

8. props

8-1. basic

  • defineProps 사용
  • 컴포넌트 내에서 한번만 사용 가능
defineProps(['foo', 'bar']);
// <p>{{ foo }}</p> // in template

// or

const props = defineProps([ 'foo' ]);
// <p>{{ props.foo }}</p> // in template
console.log(props.foo);   // in script

// or

const props = defineProps({
    foo: {
        type: String,
        required: true
    },
    bar: {
        type: Number,
        default: 1
    }
});

8-2. with typescript

const props = defineProps<{
    foo: string,
    bar?: number
}>();

// or

interface Props {
    foo: string;
    bar?: number;
}

const props = defineProps<Props>();

// default value
const props = withDefaults(defineProps<Props>(), {
    foo: 'foo'
});

9. emits

9-1. basic

  • defineEmits 사용
  • 컴포넌트 내에서 한번만 사용 가능
defineEmits([ 'foo', 'bar' ]);
// <button @click="$emit('bar')"></button> // in template

// or

const emits = defineEmits([ 'bar' ]);
// <button @click="$emit('bar')"></button> // in template
emits('bar');                              // in script

9-2. with typescript

const emits = defineEmits<{
    (e: 'foo', id: number): void
    (e: 'bar', value: string): void
}>();

10. nextTick

import { nextTick } from 'vue';

function foo() {
    nextTick(() => {
        // your code
    });
}

11. lifecycle hooks

import { onMounted, onUpdated } from 'vue';

onMounted(() => {
    console.log('mounted');
});

onUpdated(() => {
    console.log('mounted');
});

12. root emelent

  • 컴포넌트 template 내 root element를 두개이상 사용할 수 있음
<!-- vue2 no -->
<!-- vue3 ok -->
<template>
    <div></div>
    <div></div>
</template>

13. VUE SFC coding style

script setup 방식 사용시.
파일 전체를 설명하는 주석 부터, 각 영역을 분리하는 가이드라인 주석에 대한 예제입니다.
가이드라인 주석 외의 주석은 기존 주석 다는 형식으로 진행하면 됩니다.

<!--
  !! vue 파일에 대한 설명을 기술합니다. 아래는 샘플입니다.
  
  template-pack 상세 화면
  - 상단 template-pack 정보
  - 하단 탭뷰
    - Template tab
      - 좌측 : template 목록 나열 (아코디언)
      - 우측 : 선택된 template 상세
    - Variable tab
      - 변수 그룹 및 그룹내 변수 (아코디언)
-->
<template>
  <div class="common-layout">
    <el-container>
      <el-main>
        <el-container>
          <el-scrollbar>
            <el-main v-if="model" class="detail_main">
              <!-- =========================================================================== 상단 template-pack 정보 -->
              <el-row>
                <el-col :span="24">
                  <el-row class="q-my-sm">
                    <el-col :span="12">
                      <el-button type="primary" @click="handleDeleteTmplPack">삭제</el-button>
                    </el-col>
                    <el-col :span="12" style="text-align: right">
                      <el-button type="primary" @click="handleSave">상태 저장</el-button>
                    </el-col>
                  </el-row>
                </el-col>
              </el-row>

              <el-tabs v-model="activeTab">
                <!-- =================================================================================== Template tab-->
                <el-tab-pane label="Template" name="tmpl">
                  <el-row class="full-height">
                    <el-col :span="24">
                      <div class="row full-height">
                        <div class="detail_content q-pa-md col-9">
                          <template-detail :tmpl="currentTemplate" v-if="currentTemplate"></template-detail>
                        </div>
                      </div>
                    </el-col>
                  </el-row>
                </el-tab-pane>
                <!-- ================================================================================== Variables tab-->
                <el-tab-pane label="Variables" name="variableGroups">
                  <el-row justify="end">
                    <el-col :span="12" style="text-align: right">
                      <el-button type="primary" @click="handleNewVarGroup">변수 그룹 추가</el-button>
                    </el-col>
                  </el-row>
                </el-tab-pane>
              </el-tabs>
            </el-main>
          </el-scrollbar>
        </el-container>
      </el-main>
    </el-container>
  </div>
</template>

<script lang="ts" setup>
import {inject, onMounted, Ref, ref, watch} from 'vue';

// region =================================================================================================== initialize

// ------------------------------------------------------------------------- props
interface Props {
  table?: Table,
  tmplPack?: TemplatePack,
  projectConfig?: ProjectConfig
}

const props = withDefaults(defineProps<Props>(), {});

// ------------------------------------------------------------------------- models

const model: Ref<Table|null> = ref(null); // table 모델
const selected = ref([]); // 선택된 컬럼 목록

// 템플릿
const tmplPack:Ref<TemplatePack|undefined> = ref();

// ------------------------------------------------------------------------- services
const javaService = new JavaService();

// ------------------------------------------------------------------------- lifecycle, event, etc...
// declare PackEvents
// provides from template-pack.vue
const packMitt = inject<Emitter<PackEvents>>('PackEvents');

// declare TmplEvents
const tmplMitt = mitt<TmplEvents>();
provide('TmplEvents', tmplMitt);

tmplMitt.on('tmpl:save', (param:any) => {
  packMitt?.emit('pack:update', model.value!);
});

onMounted(async () => {
  await init();
});

watch(() => props.table, (val) => {
  init();
});

// endregion |end| initialize

// region ====================================================================================================== methods

/**
 * 초기화
 */
const init = async () => {
  console.log('init!!!!!');
}

/**
 * ...
 */
const filterDataType = (column:Column) => {
  if(column.decimalDigits === 0) {
    return `${column.type}(${column.size})`;
  }
  return `${column.type}(${column.size}, ${column.decimalDigits})`;
}


// ------------------------------------------------------------------------- event handler

/**
 * ...
 */
const handleSave = () => {
  tableMitt?.emit('tableSave', model.value!);
}

/**
 * ...
 */
const handleGenerate = async () => {
  console.log('generated!!')
}
// endregion |end| methods

</script>

<style scoped lang="scss">
</style>

다음은 간략 버전입니다.

<!--

-->
<template>
  <div></div>
</template>

<script lang="ts" setup>
import {onMounted, Ref, ref} from 'vue';
// region =================================================================================================== initialize

// ------------------------------------------------------------------------- props
interface Props {
  title?: string
}

const props = withDefaults(defineProps<Props>(), {});

// ------------------------------------------------------------------------- models
const model: Ref<string|null> = ref('sample');

// ------------------------------------------------------------------------- services
const greetingService = new GreetingService();

// ------------------------------------------------------------------------- lifecycle, event, etc...
onMounted(async () => {
  console.log('mounted!!!!!');
});

// endregion |end| initialize

// region ====================================================================================================== methods
const init = async () => {
  console.log('init!!!!!');
}

// ------------------------------------------------------------------------- event handler
const handleSave = () => {
  console.log('saved!!!!!');
}

// endregion |end| methods

</script>

<style scoped lang="scss">
</style>