Referensi lengkap Svelte 5 dengan Runes. State management, reactivity, components, dan fitur modern Svelte. Performa mantap!
Cara memulai menggunakan Svelte untuk membuat aplikasi web dengan performa tinggi.
Langkah-langkah untuk menginstall dan menjalankan Svelte.
# Create new project
npm create svelte@latest my-app
# Install dependencies
cd my-app
npm install
# Start dev server
npm run devStruktur dasar komponen Svelte dengan script, markup, dan styling.
<script>
let count = 0
function increment() {
count += 1
}
</script>
<button on:click={increment}>
Count: {count}
</button>
<style>
button {
background: blue;
color: white;
}
</style>Fitur baru di Svelte 5 untuk state management dan reactivity yang lebih powerful.
Cara membuat reactive state di Svelte 5 menggunakan rune $state.
<script>
// Reactive state (Svelte 5)
let count = $state(0)
let user = $state({ name: 'Budi', age: 25 })
function increment() {
count += 1
}
function updateName() {
user.name = 'Ani'
}
</script>
<button on:click={increment}>
{count}
</button>
<p>{user.name}</p><script>
let count = $state(0)
// Derived state (auto-computed)
let doubled = $derived(count * 2)
let message = $derived(`Count is ${count}`)
// Derived dengan complex logic
let status = $derived(() => {
if (count < 5) return 'low'
if (count < 10) return 'medium'
return 'high'
})
</script>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<p>Status: {status}</p><script>
let count = $state(0)
// Run side effects
$effect(() => {
console.log(`Count is now ${count}`)
})
// Effect dengan cleanup
$effect(() => {
const interval = setInterval(() => {
count += 1
}, 1000)
return () => {
clearInterval(interval)
}
})
// Pre-effect (runs before DOM updates)
$effect.pre(() => {
console.log('Before update')
})
// Root effect (no auto-tracking)
$effect.root(() => {
console.log('This runs once')
})
</script><script>
// Declare props (Svelte 5)
let { title, count = 0, user } = $props()
// Dengan fallback
let { name = 'Guest' } = $props()
// Rest props
let { class: className, ...rest } = $props()
</script>
<h1>{title}</h1>
<p>{count}</p><!-- Child.svelte -->
<script>
// Bindable prop (two-way binding)
let { value = $bindable() } = $props()
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
let text = $state('')
</script>
<Child bind:value={text} />
<p>{text}</p><script>
let count = 0
// Reactive statement
$: doubled = count * 2
$: console.log('Count changed:', count)
// Reactive block
$: {
console.log('Count is', count)
if (count > 10) {
console.log('Too high!')
}
}
// Reactive array
let numbers = [1, 2, 3]
$: sum = numbers.reduce((a, b) => a + b, 0)
</script><script>
let count = 0
let history = []
// Update reactively
function increment() {
count += 1
history = [...history, count] // Trigger reactivity
}
// Array methods that DON'T trigger reactivity
function badPush() {
history.push(count) // Won't trigger update
}
// Fix dengan assignment
function goodPush() {
history.push(count)
history = history // Trigger reactivity
}
</script><script>
let name = 'Budi'
let count = 5
</script>
<!-- Text interpolation -->
<p>{name}</p>
<p>{count + 1}</p>
<p>{name.toUpperCase()}</p>
<!-- Raw HTML (hati-hati XSS!) -->
<div>{@html htmlString}</div>
<!-- Debug -->
{@debug count, name}<script>
let user = { loggedIn: false }
let count = 5
</script>
<!-- if -->
{#if user.loggedIn}
<p>Welcome back!</p>
{/if}
<!-- if-else -->
{#if count > 10}
<p>High</p>
{:else}
<p>Low</p>
{/if}
<!-- if-else if-else -->
{#if count < 5}
<p>Low</p>
{:else if count < 10}
<p>Medium</p>
{:else}
<p>High</p>
{/if}<script>
let items = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Orange' }
]
</script>
<!-- Basic loop -->
{#each items as item}
<p>{item.name}</p>
{/each}
<!-- With index -->
{#each items as item, index}
<p>{index + 1}. {item.name}</p>
{/each}
<!-- With key (important!) -->
{#each items as item (item.id)}
<p>{item.name}</p>
{/each}
<!-- Destructuring -->
{#each items as { id, name }}
<p>{name}</p>
{/each}
<!-- With else -->
{#each items as item}
<p>{item.name}</p>
{:else}
<p>No items</p>
{/each}<script>
async function fetchData() {
const response = await fetch('/api/data')
return response.json()
}
let promise = fetchData()
</script>
{#await promise}
<p>Loading...</p>
{:then data}
<p>Got {data.length} items</p>
{:catch error}
<p>Error: {error.message}</p>
{/await}
<!-- Short form -->
{#await promise then data}
<p>{data}</p>
{/await}<script>
function handleClick() {
console.log('Clicked!')
}
function handleInput(event) {
console.log(event.target.value)
}
</script>
<!-- Inline handler -->
<button on:click={() => console.log('Clicked')}>
Click me
</button>
<!-- Function handler -->
<button on:click={handleClick}>
Click me
</button>
<!-- With event -->
<input on:input={handleInput} />
<!-- Multiple events -->
<button on:click={handleClick} on:mouseenter={() => {}}>
Hover or click
</button><!-- Prevent default -->
<form on:submit|preventDefault={handleSubmit}>
<!-- Stop propagation -->
<button on:click|stopPropagation={handleClick}>
<!-- Only once -->
<button on:click|once={handleClick}>
<!-- Capture phase -->
<div on:click|capture={handleClick}>
<!-- Chain modifiers -->
<form on:submit|preventDefault|stopPropagation={handleSubmit}>
<!-- Self (only if event.target is element itself) -->
<div on:click|self={handleClick}>
<!-- Passive (for better scroll performance) -->
<div on:wheel|passive={handleWheel}>
<!-- Non-passive -->
<div on:touchstart|nonpassive={handleTouch}>
<!-- Trusted (only if trusted event) -->
<button on:click|trusted={handleClick}><script>
let name = ''
let message = ''
let checked = false
let selected = ''
let group = []
let value = 5
</script>
<!-- Text input -->
<input bind:value={name} />
<!-- Textarea -->
<textarea bind:value={message}></textarea>
<!-- Checkbox -->
<input type="checkbox" bind:checked />
<!-- Radio -->
<input type="radio" bind:group={selected} value="a" />
<input type="radio" bind:group={selected} value="b" />
<!-- Checkbox group -->
<input type="checkbox" bind:group value="a" />
<input type="checkbox" bind:group value="b" />
<!-- Select -->
<select bind:value={selected}>
<option value="a">A</option>
<option value="b">B</option>
</select>
<!-- Range -->
<input type="range" bind:value min="0" max="10" />
<!-- Number -->
<input type="number" bind:value />
<!-- File -->
<input type="file" bind:files /><!-- Child.svelte -->
<script>
export let value
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
let text = ''
</script>
<Child bind:value={text} />
<p>{text}</p><script>
let div
let input
$: console.log(div) // DOM element
</script>
<div bind:this={div}>Content</div>
<input bind:this={input} />
<!-- Dimensions -->
<div bind:clientWidth={width} bind:clientHeight={height}>
<!-- Video/Audio -->
<video
bind:currentTime
bind:duration
bind:paused
bind:volume
></video><!-- Button.svelte -->
<script>
// Props (classic way)
export let variant = 'primary'
export let disabled = false
// Svelte 5 way
let { variant = 'primary', disabled = false } = $props()
</script>
<button class={variant} {disabled}>
<slot />
</button>
<style>
.primary {
background: blue;
}
</style><script>
import Button from './Button.svelte'
</script>
<Button variant="primary">Click me</Button>
<Button variant="secondary" disabled>Disabled</Button><!-- Card.svelte -->
<div class="card">
<div class="header">
<slot name="header" />
</div>
<div class="body">
<slot /> <!-- Default slot -->
</div>
<div class="footer">
<slot name="footer" />
</div>
</div>
<!-- Usage -->
<Card>
<h1 slot="header">Title</h1>
<p>Content here</p>
<button slot="footer">OK</button>
</Card><!-- List.svelte -->
<script>
export let items
</script>
{#each items as item}
<slot item={item} />
{/each}
<!-- Usage -->
<List items={users} let:item>
<p>{item.name}</p>
</List><script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte'
onMount(() => {
console.log('Component mounted')
// Return cleanup function
return () => {
console.log('Cleanup')
}
})
onDestroy(() => {
console.log('Component destroyed')
})
beforeUpdate(() => {
console.log('Before update')
})
afterUpdate(() => {
console.log('After update')
})
</script><script>
// Use $effect instead
$effect(() => {
console.log('Mounted / Updated')
return () => {
console.log('Cleanup')
}
})
</script>// stores.js
import { writable } from 'svelte/store'
export const count = writable(0)
export const user = writable({
name: 'Budi',
email: 'budi@example.com'
})<script>
import { count } from './stores.js'
// Subscribe with $
// Auto-subscribe dan auto-unsubscribe
$count
// Update
function increment() {
count.update(n => n + 1)
}
// Set
function reset() {
count.set(0)
}
// Manual subscribe (avoid kalo bisa)
const unsubscribe = count.subscribe(value => {
console.log(value)
})
onDestroy(unsubscribe)
</script>
<p>{$count}</p>
<button on:click={increment}>+</button>import { readable } from 'svelte/store'
export const time = readable(new Date(), set => {
const interval = setInterval(() => {
set(new Date())
}, 1000)
return () => clearInterval(interval)
})import { derived } from 'svelte/store'
import { count } from './stores.js'
export const doubled = derived(
count,
$count => $count * 2
)
// Multiple stores
export const sum = derived(
[count1, count2],
([$count1, $count2]) => $count1 + $count2
)import { writable } from 'svelte/store'
function createCounter() {
const { subscribe, set, update } = writable(0)
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
}
}
export const counter = createCounter()<script>
import { fade, fly, slide, scale } from 'svelte/transition'
let visible = true
</script>
{#if visible}
<div transition:fade>Fade in/out</div>
<div transition:fly={{ y: 200, duration: 500 }}>
Fly in/out
</div>
<div transition:slide>Slide in/out</div>
<div transition:scale>Scale in/out</div>
{/if}
<!-- Separate in/out -->
<div in:fade out:slide>
Different transitions
</div><script>
import { fly, fade } from 'svelte/transition'
</script>
<div
transition:fly={{
y: 200,
duration: 500,
delay: 100,
easing: cubicOut
}}
>
Content
</div>
<div
transition:fade={{
duration: 300
}}
>
Fade
</div>function typewriter(node, { speed = 1 }) {
const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE
if (!valid) return {}
const text = node.textContent
const duration = text.length / (speed * 0.01)
return {
duration,
tick: t => {
const i = Math.trunc(text.length * t)
node.textContent = text.slice(0, i)
}
}
}<script>
import { flip } from 'svelte/animate'
import { quintOut } from 'svelte/easing'
let items = [1, 2, 3, 4, 5]
function shuffle() {
items = items.sort(() => Math.random() - 0.5)
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each items as item (item)}
<div animate:flip={{ duration: 300, easing: quintOut }}>
{item}
</div>
{/each}<script>
function clickOutside(node) {
function handleClick(event) {
if (!node.contains(event.target)) {
node.dispatchEvent(new CustomEvent('outclick'))
}
}
document.addEventListener('click', handleClick, true)
return {
destroy() {
document.removeEventListener('click', handleClick, true)
}
}
}
</script>
<div use:clickOutside on:outclick={() => console.log('Clicked outside')}>
Content
</div>
<!-- With parameters -->
<div use:action={params}>src/routes/
├── +page.svelte # /
├── about/
│ └── +page.svelte # /about
├── blog/
│ ├── +page.svelte # /blog
│ └── [slug]/
│ └── +page.svelte # /blog/[slug]
└── api/
└── users/
└── +server.js # /api/users// +page.js
export async function load({ fetch, params }) {
const response = await fetch('/api/users')
const users = await response.json()
return {
users
}
}<!-- +page.svelte -->
<script>
export let data
</script>
<h1>Users</h1>
{#each data.users as user}
<p>{user.name}</p>
{/each}// +page.server.js
export const actions = {
default: async ({ request }) => {
const data = await request.formData()
const email = data.get('email')
// Process form
await saveToDatabase(email)
return { success: true }
}
}<!-- +page.svelte -->
<script>
export let form
</script>
<form method="POST">
<input name="email" type="email" />
<button>Submit</button>
</form>
{#if form?.success}
<p>Success!</p>
{/if}<!-- Bad -->
<script>
let items = []
items.push(newItem) // Won't trigger reactivity
</script>
<!-- Good -->
<script>
let items = []
items = [...items, newItem]
</script><!-- Bad -->
<script>
export let user
user.name = 'Ani' // Mutating prop
</script>
<!-- Good -->
<script>
export let user
let localUser = { ...user }
localUser.name = 'Ani'
</script><!-- Bad - no key -->
{#each items as item}
<div>{item.name}</div>
{/each}
<!-- Bad - index as key -->
{#each items as item, i (i)}
<div>{item.name}</div>
{/each}
<!-- Good - unique ID as key -->
{#each items as item (item.id)}
<div>{item.name}</div>
{/each}Login atau daftar akun gratis untuk membaca cheat sheet ini.