Referensi lengkap Vue 3 dengan Composition API. Reactive state, lifecycle, components, dan fitur modern Vue. Perfect buat frontend development.
Panduan awal untuk memulai pengembangan aplikasi dengan Vue.js framework.
Cara menginstall Vue.js menggunakan berbagai method seperti npm atau CDN.
# Pake npm
npm create vue@latest
# Pake pnpm
pnpm create vue@latest
# Via CDN
<script src="https://unpkg.com/vue@3"></script>Cara membuat aplikasi Vue.js dasar dengan setup minimal.
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')Sintaks template Vue.js untuk menampilkan data dan mengatur interaksi.
Cara menampilkan data JavaScript dalam template menggunakan double curly braces.
<template>
<p>{{ message }}</p>
<p>{{ count + 1 }}</p>
<p>{{ ok ? 'YES' : 'NO' }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
</template>Directive Vue.js untuk mengatur behavior element HTML dengan berbagai fungsi.
<template>
<!-- v-bind - Attribute binding -->
<img v-bind:src="imageUrl" />
<img :src="imageUrl" /> <!-- Shorthand -->
<div :class="{ active: isActive }"></div>
<div :style="{ color: textColor }"></div>
<!-- v-on - Event handling -->
<button v-on:click="handleClick">Click</button>
<button @click="handleClick">Click</button> <!-- Shorthand -->
<input @input="handleInput" />
<!-- v-model - Two-way binding -->
<input v-model="message" />
<textarea v-model="text"></textarea>
<input type="checkbox" v-model="checked" />
<select v-model="selected">
<option>A</option>
<option>B</option>
</select>
<!-- v-if, v-else-if, v-else -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Not A or B</div>
<!-- v-show (CSS display) -->
<div v-show="isVisible">Show me</div>
<!-- v-for - List rendering -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<div v-for="(item, index) in items" :key="index">
{{ index }}: {{ item }}
</div>
<div v-for="(value, key) in object" :key="key">
{{ key }}: {{ value }}
</div>
</template>API baru Vue 3 untuk mengorganisir logic komponen dengan cara yang lebih fleksibel.
Fungsi setup yang menjadi entry point untuk Composition API dalam komponen.
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const user = reactive({
name: 'Budi',
age: 25
})
function increment() {
count.value++
}
return {
count,
user,
increment
}
}
}
</script>Sintaks script setup yang lebih sederhana untuk menggunakan Composition API.
<script setup>
import { ref, reactive } from 'vue'
// Gak perlu return, auto-exposed ke template
const count = ref(0)
const user = reactive({
name: 'Budi',
age: 25
})
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
<p>{{ user.name }}</p>
</template>Sistem reaktivitas Vue.js yang secara otomatis mengupdate UI ketika data berubah.
Cara membuat reactive reference untuk primitive values.
<script setup>
import { ref } from 'vue'
// Reactive reference
const count = ref(0)
const message = ref('Hello')
// Access value dengan .value
console.log(count.value) // 0
count.value++
// Di template gak perlu .value
</script>
<template>
<p>{{ count }}</p>
</template><script setup>
import { reactive } from 'vue'
// Reactive object
const state = reactive({
count: 0,
message: 'Hello'
})
// Akses langsung, gak pake .value
state.count++
state.message = 'Hi'
// Nested objects juga reactive
const user = reactive({
profile: {
name: 'Budi',
email: 'budi@example.com'
}
})
user.profile.name = 'Ani' // Reactive
</script><script setup>
import { ref, computed } from 'vue'
const firstName = ref('Budi')
const lastName = ref('Santoso')
// Computed property
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
// Writable computed
const fullNameWritable = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(value) {
[firstName.value, lastName.value] = value.split(' ')
}
})
</script>
<template>
<p>{{ fullName }}</p>
</template><script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({ name: 'Budi' })
// Watch specific source
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
})
// Watch multiple sources
watch([count, () => user.name], ([newCount, newName]) => {
console.log(`Count: ${newCount}, Name: ${newName}`)
})
// Deep watch untuk object
watch(
() => user,
(newUser) => {
console.log('User changed:', newUser)
},
{ deep: true }
)
// watchEffect - auto track dependencies
watchEffect(() => {
console.log(`Count is ${count.value}`)
})
// watchEffect dengan cleanup
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log(count.value)
}, 1000)
onCleanup(() => {
clearTimeout(timer)
})
})
</script><script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// Before component is mounted
onBeforeMount(() => {
console.log('Before Mount')
})
// After component is mounted
onMounted(() => {
console.log('Mounted')
// Good place buat API calls, DOM manipulation
})
// Before component updates
onBeforeUpdate(() => {
console.log('Before Update')
})
// After component updates
onUpdated(() => {
console.log('Updated')
})
// Before component unmounts
onBeforeUnmount(() => {
console.log('Before Unmount')
// Cleanup subscriptions, timers, etc
})
// After component unmounts
onUnmounted(() => {
console.log('Unmounted')
})
</script><!-- MyComponent.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<style scoped>
button {
background: blue;
color: white;
}
</style><script setup>
// Define props
const props = defineProps({
title: String,
count: {
type: Number,
default: 0,
required: true
},
user: Object,
tags: Array
})
// Dengan TypeScript
const props = defineProps<{
title: string
count?: number
user: { name: string; age: number }
}>()
// Access props
console.log(props.title)
console.log(props.count)
</script>
<template>
<h1>{{ title }}</h1>
<p>{{ count }}</p>
</template><script setup>
// Define emits
const emit = defineEmits(['update', 'delete'])
// Dengan validation
const emit = defineEmits({
update: (value) => {
return typeof value === 'string'
},
delete: null
})
// Dengan TypeScript
const emit = defineEmits<{
update: [value: string]
delete: [id: number]
}>()
// Emit event
function handleUpdate() {
emit('update', 'new value')
}
function handleDelete() {
emit('delete', 123)
}
</script>
<template>
<button @click="handleUpdate">Update</button>
<button @click="handleDelete">Delete</button>
</template><!-- Parent -->
<template>
<CustomInput v-model="text" />
</template>
<!-- CustomInput.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function handleInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input
:value="modelValue"
@input="handleInput"
/>
</template><!-- Parent -->
<template>
<Card>
<template #header>
<h1>Title</h1>
</template>
<template #default>
<p>Content here</p>
</template>
<template #footer>
<button>OK</button>
</template>
</Card>
</template>
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot></slot> <!-- Default slot -->
</div>
<div class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template><!-- Parent -->
<template>
<UserList>
<template #default="{ user }">
<p>{{ user.name }} - {{ user.email }}</p>
</template>
</UserList>
</template>
<!-- UserList.vue -->
<script setup>
const users = ref([
{ id: 1, name: 'Budi', email: 'budi@example.com' },
{ id: 2, name: 'Ani', email: 'ani@example.com' }
])
</script>
<template>
<div v-for="user in users" :key="user.id">
<slot :user="user"></slot>
</div>
</template>// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
doubled,
increment,
decrement,
reset
}
}<script setup>
import { useCounter } from './composables/useCounter'
const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>
<template>
<p>Count: {{ count }}</p>
<p>Doubled: {{ doubled }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</template>// useFetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
return { data, error, loading, fetchData }
}
// useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(
JSON.parse(localStorage.getItem(key)) ?? defaultValue
)
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}npm install pinia// main.js
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
// Getters
const doubled = computed(() => count.value * 2)
// Actions
function increment() {
count.value++
}
function decrement() {
count.value--
}
return {
count,
doubled,
increment,
decrement
}
})
// Options API style
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubled: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// Access state
console.log(counter.count)
// Access getters
console.log(counter.doubled)
// Call actions
counter.increment()
// Destructure dengan storeToRefs
import { storeToRefs } from 'pinia'
const { count, doubled } = storeToRefs(counter)
const { increment, decrement } = counter
</script>npm install vue-router@4// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue') // Lazy loading
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Navigate
function goToAbout() {
router.push('/about')
router.push({ name: 'about' })
router.push({ path: '/user/123' })
}
// Access params
console.log(route.params.id)
// Access query
console.log(route.query.search)
</script>
<template>
<!-- router-link -->
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'about' }">About</router-link>
<!-- router-view -->
<router-view />
</template><template>
<!-- v-text -->
<span v-text="message"></span>
<!-- v-html (hati-hati XSS!) -->
<div v-html="htmlContent"></div>
<!-- v-cloak -->
<div v-cloak>{{ message }}</div>
<!-- v-once - render sekali -->
<span v-once>{{ message }}</span>
<!-- v-memo - memoize subtree -->
<div v-memo="[count]">
{{ count }}
</div>
</template><script setup>
// Directive definition
const vFocus = {
mounted(el) {
el.focus()
}
}
const vColor = {
mounted(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
</script>
<template>
<input v-focus />
<p v-color="'red'">Red text</p>
</template><template>
<!-- Teleport ke body -->
<Teleport to="body">
<div class="modal">
<p>Modal content</p>
</div>
</Teleport>
<!-- Teleport ke selector -->
<Teleport to="#modal-container">
<div>Content</div>
</Teleport>
</template><template>
<Suspense>
<!-- Component yang async -->
<template #default>
<AsyncComponent />
</template>
<!-- Loading state -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
// AsyncComponent.vue
const data = await fetch('/api/data').then(r => r.json())
</script><script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// Computed - cached, cuma re-evaluate kalo dependencies berubah
const doubled = computed(() => count.value * 2)
// Method - always execute
function getDoubled() {
return count.value * 2
}
</script>
<template>
<!-- Computed - efficient -->
<p>{{ doubled }}</p>
<!-- Method - execute setiap render -->
<p>{{ getDoubled() }}</p>
</template><script setup>
// 1. Imports
import { ref, computed, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'
// 2. Props & Emits
const props = defineProps(['title'])
const emit = defineEmits(['update'])
// 3. State
const count = ref(0)
// 4. Computed
const doubled = computed(() => count.value * 2)
// 5. Functions
function increment() {
count.value++
}
// 6. Lifecycle
onMounted(() => {
console.log('Mounted')
})
</script><script setup>
import { shallowRef, shallowReactive } from 'vue'
// Shallow reactivity - cuma top-level yang reactive
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// Bisa update nested tapi gak trigger reactivity
state.nested.bar = 3 // Won't trigger update
// Update top-level bakal trigger
state.foo = 2 // Will trigger
</script><script setup>
// Set component options
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
</script>Login atau daftar akun gratis untuk membaca cheat sheet ini.