Пытаюсь успешно ввести ActionResults для действий формы в SvelteKit 2

Вопрос или проблема

Я пытаюсь вернуть созданный объект из POST-запроса на страницу. Всё работает, но сервер разработки Svelte Vite (который запущен) похоже не понимает типы должным образом, в результате мне приходится использовать any, чтобы получить нужные данные.

У действия правильный выведенный тип возврата, когда я на него наводю курсор, но result в колбэке улучшателя застрял с типом: ActionResult<Record<string, unknown> | undefined, Record<string, unknown> | undefined>.

Вот соответствующее действие в +page.server.ts:

export const actions: Actions = {
    newPage: async ({ locals }) => {
        if (locals.user == null) return fail(403);

        const project = await getUserDefaultProject(locals.dbclient, locals.user.id);
        if (project == null) return fail(500);

        const newPage = await createNewPage(locals.dbclient, locals.user, project.id);
        if (newPage) return newPage;

        return fail(400);
    },
};

(newPage имеет тип <PageMetadata|null>)

и код улучшенной формы в +page.svelte:

<form
    action="?/newPage"
    method="POST"
    use:enhance={({ cancel }) => {
        if (!loggedIn.state || offlineMode.state) {
            cancel();
            createNewLocalPage();
            return;
        }
        return async ({ result }) => {
            if (result.status == 200) {
                const newPage = (result as any).data as PageMetadata;
                pages.state = [...pages.state, newPage];
            }
        };
    }}
>
    <button type="submit" title="создать новую страницу" id="create-new-page">
        <Fa icon={faPlus} color="var(--background)" />
        <div>новая страница</div>
    </button>
</form>

Я искал, как правильно типизировать это, но ничего не нашёл, поэтому буду признателен за любую помощь.

Без минимально воспроизводимого кода я не могу дать вам очень полный ответ, но я полагаю, что замена if (result.status == 200) на if (result.type === 'success') для проверки вашего результата в улучшателе должна решить проблему, ваша форма будет выглядеть так:

<form
    action="?/newPage"
    method="POST"
    use:enhance={({ cancel }) => {
        if (!loggedIn.state || offlineMode.state) {
            cancel();
            createNewLocalPage();
            return;
        }
        return async ({ result }) => {
            if (result.type === 'success'){
                const newPage = result.data;
                pages.state = [...pages.state, newPage];
            }
        };
    }}
>
    <button type="submit" title="создать новую страницу" id="create-new-page">
        <Fa icon={faPlus} color="var(--background)" />
        <div>новая страница</div>
    </button>
</form>

Объяснение: согласно документации причина, по которой вы можете получать ошибку, такую как Property 'data' does not exist on type 'ActionResult<Record<string, unknown> | undefined, Record<string, unknown> | undefined>', может быть связана с проверкой результата с result.status === 200, потому что это удовлетворяет всем формам типу возврата, где некоторые не имеют свойства data. Typescript примет поиск result.data только в том случае, если ваша проверка гарантирует форму, в которой свойство существует; это будет result.type === 'success' || result.type === 'failure'.

ActionResult
Когда вы вызываете действие формы через fetch, ответ будет одной из этих форм.

<form method="post" use:enhance={() => {
  return ({ result }) => {
      // result имеет тип ActionResult
  };
}}
type ActionResult<
  Success extends
      | Record<string, unknown>
      | undefined = Record<string, any>,
  Failure extends
      | Record<string, unknown>
      | undefined = Record<string, any>
> =
  | { type: 'success'; status: number; data?: Success }
  | { type: 'failure'; status: number; data?: Failure }
  | { type: 'redirect'; status: number; location: string }
  | { type: 'error'; status?: number; error: any };

Ответ или решение

Работа с формами в SvelteKit может стать вызовом, особенно когда дело доходит до правильного типизирования ActionResults. Давайте рассмотрим вашу проблему более подробно и найдем оптимальное решение.

Проблема: Неправильное понимание типов в ActionResults

Вы столкнулись с ситуацией, когда возвращаемый объект из POST-запроса к действию формы не типизируется должным образом. При попытке доступа к данным, возвращаемым из вашего действия, TypeScript ограничивает вас, указывая на тип ActionResult<Record<string, unknown> | undefined, Record<string, unknown> | undefined>, что приводит к необходимости использовать any для обойти эту проблему.

Решение: Правильное использование ActionResult

Ваш код в +page.server.ts выглядит корректно:

export const actions: Actions = {
    newPage: async ({ locals }) => {
        if (locals.user == null) return fail(403);

        const project = await getUserDefaultProject(locals.dbclient, locals.user.id);
        if (project == null) return fail(500);

        const newPage = await createNewPage(locals.dbclient, locals.user, project.id);
        if (newPage) return newPage;

        return fail(400);
    },
};

Оптимизация: Изменение проверки результата

Как вы уже заметили, проблема с типами может быть решена изменением способа валидации результата в функции-обработчике use:enhance. Вместо проверки result.status == 200, вы можете использовать проверку result.type === 'success'. Это более надёжный способ, так как каждая ветвь результата имеет более строгую типизацию:

<form
    action="?/newPage"
    method="POST"
    use:enhance={({ cancel }) => {
        if (!loggedIn.state || offlineMode.state) {
            cancel();
            createNewLocalPage();
            return;
        }
        return async ({ result }) => {
            if (result.type === 'success') {
                const newPage = result.data;
                pages.state = [...pages.state, newPage];
            }
        };
    }}
>
    <button type="submit" title="создать новую страницу" id="create-new-page">
        <Fa icon={faPlus} color="var(--background)" />
        <div>новая страница</div>
    </button>
</form>

Почему это работает?

Используя result.type, вы обеспечиваете TypeScript необходимой информацией о структуре результата. Различные типы результата (success, failure, redirect, error) имеют свои поля, и проверка result.type === 'success' позволяет избежать неприятных ошибок. Если type является success, тогда data гарантированно будет присутствовать, что позволяет вамtype указать более точный тип:

if (result.type === 'success') {
    const newPage: PageMetadata = result.data; // теперь TypeScript понимает, что здесь находится PageMetadata
}

Заключение

Работа с типами в SvelteKit может быть сложной, но правильные проверки и использование встроенных возможностей TypeScript позволяет значительно упростить код и повысить его читаемость. Используя предложенные изменения, вы сможете избежать неясностей с типами и повысить качество вашего кода.

В случае дополнительных вопросов или сложностей с реализацией всегда можно обратиться к документации Svelte и TypeScript, которая может предоставить больше информации о том, как правильно работать с типами и обрабатывать различные результаты действий.

Оцените материал
Добавить комментарий

Капча загружается...