プログラミング/svelte/svelte5への移行 の履歴(No.2)
更新新しいバージョンではかなりいろいろ変わるらしい†
Svelte 5†
- export let で定義していたプロパティは $props() で定義するようになった
- 省略可能なものはタイプ名を ? 付きにする
- bind で使うものはさらに $bindable() とする
- <slot /> ではなく Snippet を {@render } するようになった
- on:click の形で Event Forwarding することはできなくなったので、正攻法で oninput?: FormEventHandler<HTMLInputElement> のようなプロパティを定義することになる
src/lib/components/Dialog.svelte
LANG: html
<script lang="ts">
import type { Snippet } from 'svelte';
let { title, children }: { title: string; children: Snippet } = $props();
</script>
<div class="dialog relative flex min-h-screen flex-col items-center justify-center">
<div class="m-auto w-full rounded-md p-6 shadow-2xl lg:max-w-lg">
<h1 class="text-center text-3xl font-semibold text-primary">{title}</h1>
{@render children()}
</div>
</div>
src/lib/components/Form.svelte
LANG: html
<script lang="ts">
import type { Snippet } from 'svelte';
let {
message = '',
enhance = () => {
return {};
},
props = {},
children,
}: {
message?: string;
enhance?: (el: HTMLFormElement) => object;
props?: svelteHTML.IntrinsicElements['form'];
children: Snippet;
} = $props();
let form: HTMLFormElement;
$effect(() => {
if (message) {
// エラーメッセージが付いていたらすべてのコントロールをエラー表示にする
['checkbox', 'file-input', 'radio', 'range', 'select', 'ihput', 'textarea', 'toggle'].forEach(
(kind) => {
for (const elem of form.querySelectorAll(`.${kind}`)) {
elem.classList.add(`${kind}-error`);
}
}
);
}
});
</script>
{#if message}<span class="text-sm text-error">{message}</span>{/if}
<form bind:this={form} class="space-y-4" method="POST" use:enhance {...props}>
{@render children()}
</form>
src/lib/components/InputText.svelte
LANG: html
<script lang="ts">
import type { FormEventHandler } from 'svelte/elements';
import type { Writable } from 'svelte/store';
let {
name,
label,
labelAlt = '',
value = $bindable(),
disabled = false,
errors = undefined,
props = {},
oninput = () => {},
}: {
name: string;
label: string;
labelAlt?: string;
value: string;
disabled?: boolean;
errors?: Writable<{}> | undefined;
props?: svelteHTML.IntrinsicElements['input'];
oninput?: FormEventHandler<HTMLInputElement>;
} = $props();
let key = name as keyof typeof errors;
</script>
<div class="form-control w-full">
<label for={name} class="label">
<span class="label-text">{label}</span>
{#if labelAlt}<span class="label-text-alt">{labelAlt}</span>{/if}
</label>
<input
{...{ name, type: 'text', ...props }}
bind:value
{disabled}
{oninput}
class="input input-bordered input-primary w-full"
class:input-error={errors && $errors && $errors[key]}
/>
{#if errors && $errors && $errors[key]}
<label class="label" for={name}>
<span class="label-text-alt text-error">{$errors[key]}</span>
</label>
{/if}
</div>
src/lib/components/InputPassword.svelte
LANG: html
<script lang="ts">
import InputText from '$lib/components/InputText.svelte';
import type { FormEventHandler } from 'svelte/elements';
import type { Writable } from 'svelte/store';
let {
name,
label,
labelAlt = '',
value = $bindable(),
disabled = false,
errors = undefined,
props = {},
oninput = () => {},
}: {
name: string;
label: string;
labelAlt?: string;
value: string;
disabled?: boolean;
errors?: Writable<{}> | undefined;
props?: svelteHTML.IntrinsicElements['input'] | undefined;
oninput?: FormEventHandler<HTMLInputElement>;
} = $props();
</script>
<InputText
{...{ name, label, labelAlt, disabled, errors }}
props={{ type: 'password', ...props }}
bind:value
{oninput}
/>
SuperForms v2†
https://superforms.rocks/migration-v2
superValidate へ schema を渡す際に、zod のアダプタでラップしなければならなくなった。
LANG: ts
import type { Actions, PageServerLoad } from './$types';
import { schema } from './zod-email';
- import { superValidate } from 'sveltekit-superforms/server';
+ import { superValidate } from 'sveltekit-superforms';
+ import { zod } from 'sveltekit-superforms/adapters';
import { fail, redirect } from '@sveltejs/kit';
import { setFlash } from 'sveltekit-flash-message/server';
import type { purposes } from '$params/emailVerificationPurpose';
import { path } from '$lib';
export const load = (async (event) => {
- const form = await superValidate(schema);
+ const form = await superValidate(zod(schema));
const purpose = event.params.purpose as keyof typeof purposes;
return { form, purpose };
}) satisfies PageServerLoad;
export const actions: Actions = {
default: async (event) => {
// フォームデータのバリデーション
- const form = await superValidate(event, schema);
+ const form = await superValidate(event, zod(schema));
const purpose = event.params.purpose as keyof typeof purposes;
if (!form.valid) {
return fail(400, { form, purpose });
}
// TODO: emailVerification レコードを作成
// TODO: メールを送信
setFlash(
{
type: 'success',
message: 'An email was sent to the mail address for mail address validation.',
},
event
);
throw redirect(302, path('/'));
},
};
Counter: 2245 (from 2010/06/03),
today: 1,
yesterday: 5