Skip to main content

Với use:enhance, chúng ta có thể đi xa hơn việc chỉ mô phỏng hành vi tự nhiên của trình duyệt. Bằng cách cung cấp một callback (hàm gọi lại), chúng ta có thể thêm các thứ như pending statesoptimistic UI. Hãy mô phỏng việc mạng chậm bằng cách thêm độ trễ vào hai action của chúng ta:

src/routes/+page.server.js
export const actions = {
	create: async ({ cookies, request }) => {
		await new Promise((fulfil) => setTimeout(fulfil, 1000));
		...
	},

	delete: async ({ cookies, request }) => {
		await new Promise((fulfil) => setTimeout(fulfil, 1000));
		...
	}
};

Khi chúng ta tạo hoặc xóa mục, giờ đây nó mất hết một giây trước khi giao diện người dùng cập nhật, khiến người dùng tự hỏi liệu họ đã làm sai điều gì đó hay không. Để giải quyết vấn đề đó, chúng ta sẽ thêm một số local state (trạng thái cục bộ)...

src/routes/+page.svelte
<script>
	import { fly, slide } from 'svelte/transition';
	import { enhance } from '$app/forms';

	export let data;
	export let form;

	let creating = false;
	let deleting = [];
</script>

...và chuyển đổi creating bên trong use:enhance đầu tiên:

src/routes/+page.svelte
<form
	method="POST"
	action="?/create"
	use:enhance={() => {
		creating = true;

		return async ({ update }) => {
			await update();
			creating = false;
		};
	}}
>
	<label>
		thêm todo:
		<input
			disabled={creating}
			name="description"
			value={form?.description ?? ''}
			autocomplete="off"
			required
		/>
	</label>
</form>

Sau đó chúng ta có thể hiển thị một thông báo trong khi chúng ta đang lưu dữ liệu:

src/routes/+page.svelte
<ul class="todos">
	<!-- ... -->
</ul>

{#if creating}
	<span class="saving">Đang lưu...</span>
{/if}

Trong trường hợp xóa, thực sự không cần phải đợi máy chủ xác minh bất cứ thứ gì — chúng ta có thể cập nhật giao diện người dùng ngay lập tức:

src/routes/+page.svelte
<ul class="todos">
	{#each data.todos.filter((todo) => !deleting.includes(todo.id)) as todo (todo.id)}
		<li in:fly={{ y: 20 }} out:slide>
			<form
				method="POST"
				action="?/delete"
				use:enhance={() => {
					deleting = [...deleting, todo.id];
					return async ({ update }) => {
						await update();
						deleting = deleting.filter((id) => id !== todo.id);
					};
				}}
			>
				<input type="hidden" name="id" value={todo.id} />
				<button aria-label="Mark as complete"></button>

				{todo.description}
			</form>
		</li>
	{/each}
</ul>

use:enhance có thể tùy chỉnh rất nhiều — bạn có thể cancel() đơn gởi, xử lý chuyển hướng, kiểm soát xem form có được đặt lại hay không, vân vân... Xem tài liệu để biết chi tiết đầy đủ.

Tiếp theo: API routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<script>
	import { fly, slide } from 'svelte/transition';
	import { enhance } from '$app/forms';
 
	export let data;
	export let form;
</script>
 
<div class="centered">
	<h1>todos</h1>
 
	{#if form?.error}
		<p class="error">{form.error}</p>
	{/if}
 
	<form method="POST" action="?/create" use:enhance>
		<label>
			thêm todo:
			<input
				name="description"
				value={form?.description ?? ''}
				autocomplete="off"
				required
			/>
		</label>
	</form>
 
	<ul class="todos">
		{#each data.todos as todo (todo.id)}
			<li in:fly={{ y: 20 }} out:slide>
				<form method="POST" action="?/delete" use:enhance>
					<input type="hidden" name="id" value={todo.id} />
					<span>{todo.description}</span>
					<button aria-label="Mark as complete"></button>
				</form>
			</li>
		{/each}
	</ul>
</div>
 
<style>
	.centered {
		max-width: 20em;
		margin: 0 auto;
	}
 
	label {
		width: 100%;
	}
 
	input {
		flex: 1;
	}
 
	span {
		flex: 1;
	}
 
	button {
		border: none;
		background: url(./remove.svg) no-repeat 50% 50%;
		background-size: 1rem 1rem;
		cursor: pointer;
		height: 100%;
		aspect-ratio: 1;
		opacity: 0.5;
		transition: opacity 0.2s;
	}
 
	button:hover {
		opacity: 1;
	}
 
	.saving {
		opacity: 0.5;
	}
</style>
 
initialising