[WIP] 違いでわかるSvelte
※この記事は書きかけです。すでに書いてある文章も変更になる可能性があります。
みんな大好きReact/Vueと比較してSvelteではどうやるの?でSvelteのことをわかった気になるための記事です。
はじめに
公式サイト
現在のSvelteは.svelteファイルに script/style はタグで囲みつつ、template部分は空いているところに直接書けば動きます。
ちなみにroot elementは一つじゃなくてもちゃんと動きます。
<script lang="ts"> // lang属性が使える
const hoge = 'example'
</script>
<h1>hello</h1>
<div class="hoge">{hoge}</div>
<style>
.hoge { color: red; }
</style>
Reactはhooks、Vueはv3のComposition APIで紹介していきます。
Component local states
React
import { useState } from 'react'
export function Counter() {
const [count] = useState(0)
return <div>{count}</div>
}
Vue
<template>
<div>{{count}}</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
}
}
</script>
Svelte
<script>
let count = 0
</script>
<div>{count}</div>
Computed states
React
const [count] = useState(0)
const doubleCount = useMemo(() => count * 2, [count])
Vue
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
Svelte
const count = 0
$: doubleCount = count * 2
Reference
React
const divRef = useRef(undefined)
useEffect(() => {
if (divRef.current !== undefined) {
...
}
})
<div ref={divRef}>hoge</div>
Vue
<template>
<div ref="divRef">hoge</div>
</template>
...
const divRef = ref(undefined)
onMounted(() => {
if (divRef.value !== undefined) {
...
}
})
return { divRef }
Svelte
<script>
import { onMount } from 'svelte'
let divRef = undefined
onMount(() => {
if (divRef !== undefined) {
...
}
})
</script>
<div bind:this={divRef}>hoge</div>
Events (native)
React
const handleClick = () => {
...
}
<button onClick={handleClick}>hoge</button>
Vue
<template>
<button @click="handleClick">hoge</button>
</template>
...
const handleClick = () => {
...
}
return { handleClick }
Svelte
<script>
const handleClick = () => {
...
}
</script>
<button on:click={handleClick}>hoge</button>
Events (custom)
React
// Listener side
const handleCustomEvent = useCallback(({ hello }) => {
...
}, [])
<ExampleComponent onCustomEvent={handleCustomEvent}>
// Dispatcher side
props.onCustomEvent({ hello: 'world' })
Vue
// Listener side
function handleCustomEvent({ hello }) {
...
}
<example-component @custom-event="handleCustomEvent">
// Dispatcher side
setup(props, { emit }) {
emit('custom-event', { hello: 'world' })
}
Svelte
// Listener side
<ExampleComponent on:customEvent={handleCustomEvent}>
// Dispatcher side
import { createEventDispatcher } from 'svelte'
const dispatchCustomEvent = createEventDispatcher<{ customEvent: { hello: string } }>()
dispatchCustomEvent('customEvent', { hello: 'world' })
If condition
React
const showHello = true
{showHello && <div>hello</div>}
Vue
<template>
<div v-if="showHello">hello</div>
</template>
...
const showHello = true
return { showHello }
Svelte
<script>
const showHello = true
</script>
{#if showHello}
<div>hello</div>
{/if}
Loop
React
const nums = [1, 2, 3]
return (
<>
{nums.map((num) => (
<div key={num}>{num}</div>
))}
</>
)
Vue
<template>
<div v-for="num in nums" :key="num">{{num}}</div>
</template>
...
const nums = [1, 2, 3]
return { nums }
Svelte
<script>
const nums = [1, 2, 3]
</script>
{#each nums as num (num)} // ()内がkeyになる
<div>{num}</div>
{/each}
Props
React
type Props = {
hello: string
}
export function ExampleComponent(props: Props) {
...
}
Vue
import type { PropType } from 'vue'
props: {
hello: {
type: String as PropType<string>,
required: true
}
}
Svelte
export let hello: string
Slots
React
import type { ReactNode } from 'react'
type Props = {
children: ReactNode
}
export function ExampleComponent(props: Props) {
return <div>{props.children}</div>
}
Vue
<template>
<div>
<slot />
</div>
</template>
Svelte
<div>
<slot />
</div>
Named Slots
React
import type { ReactNode } from ‘react’
type Props = {
titleSlot: ReactNode
}
export function ExampleComponent(props: Props) {
return <div>{props.titleSlot}</div>
}
Vue
<template>
<div>
<slot name="title" />
</div>
</template>
Svelte
<div>
<slot name="title" />
</div>
Class (attribute)
React
const className = [
'hello',
props.active && 'is-active'
].map(Boolean).join(' ')
<div className={className}></div>
Vue
<div class="{ hello: true, 'is-active': active }"></div>
Svelte
<div class="hello" class:hello={active}></div>
Watch
React
const [count, setCount] = useState(0)
useEffect(() => {
if (count !== 0 && count % 3 === 0) {
console.log('A multiple of 3')
}
}, [count])
Vue
const count = ref(0)
watch(count, (count) => {
if (count !== 0 && count % 3 === 0) {
console.log('A multiple of 3')
}
})
Svelte
let count = 0
$: {
if (count !== 0 && count % 3 === 0) {
console.log('A multiple of 3')
}
}
🚨HTML interpolation
React
<div dangerouslySetInnerHTML={{ __html: '<p>Dager text</p>' }} />
Vue
<div v-html="<p>Danger text</p>" />
Svelte
{@html '<p>Danger text</p>'}
Lifecycles
React
useEffect(() => {
// when a component is mounted
return () => {
// when a component is destroyed
}
}, [])
useEffect(() => {
// when props.something is updated
}, [props.something])
useEffect(() => {
window.requestAnimationFrame(() => {
// next tick
})
}, [])
Vue
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated
onBeforeUnmount
onUnmounted
onErrorCaptured
onRenderTracked
onRenderTriggered
onActivated
onDeactivated,
nextTick
} from 'vue
Svelte
import {
onMount,
beforeUpdate,
afterUpdate,
onDestroy,
tick
} from 'svelte'
Await/Suspense
TODO
Teleport
TODO