Изменение данных¶
В предыдущей главе вы реализовали поиск и пагинацию с помощью URL Search Params и Next.js API. Давайте продолжим работу над страницей «Счета», добавив возможность создавать, обновлять и удалять счета!
Вот темы, которые мы рассмотрим
- Что такое React Server Actions и как их использовать для изменения данных.
- Как работать с формами и серверными компонентами.
- Лучшие практики работы с родным объектом
FormData
, включая валидацию типов. - Как пересмотреть клиентский кэш с помощью API
revalidatePath
. - Как создавать динамические сегменты маршрута с определенными идентификаторами.
Что такое серверные операции?¶
React Server Actions позволяют запускать асинхронный код непосредственно на сервере. Они устраняют необходимость создания конечных точек API для изменения данных. Вместо этого вы пишете асинхронные функции, которые выполняются на сервере и могут быть вызваны из ваших клиентских или серверных компонентов.
Безопасность является главным приоритетом для веб-приложений, поскольку они могут быть уязвимы для различных угроз. Именно здесь на помощь приходят Server Actions. Они включают такие функции, как зашифрованные закрытия, строгие проверки ввода, хэширование сообщений об ошибках, ограничения хоста и многое другое - все вместе они значительно повышают безопасность вашего приложения.
Использование форм с Server Actions¶
В React вы можете использовать атрибут action
в элементе <form>
для вызова действий. Действие автоматически получит нативный объект FormData, содержащий перехваченные данные.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Преимуществом вызова Server Actions внутри Server Component является прогрессивное улучшение - формы работают, даже если JavaScript еще не загружен на клиенте. Например, при отсутствии медленных интернет-соединений.
Next.js с Server Actions¶
Server Actions также глубоко интегрированы с Next.js кэшированием. Когда форма отправляется через Server Actions, вы можете не только использовать действие для изменения данных, но и пересмотреть связанный с ним кэш с помощью таких API, как revalidatePath
и revalidateTag
.
В чем одно из преимуществ использования Server Actions?
Давайте посмотрим, как все это работает вместе!
Создание счета-фактуры¶
Вот шаги, которые необходимо предпринять, чтобы создать новый счет-фактуру:
- Создайте форму для ввода данных пользователем.
- Создайте Server Actions и вызовите его из формы.
- Внутри действия сервера извлеките данные из объекта
formData
. - Проверьте и подготовьте данные для вставки в базу данных.
- Вставьте данные и обработайте все ошибки.
- Переопределите кэш и перенаправьте пользователя обратно на страницу счетов.
1. Создайте новый маршрут и форму¶
Для начала внутри папки /invoices
добавьте новый сегмент маршрута /create
с файлом page.tsx
:
Вы будете использовать этот маршрут для создания новых счетов-фактур. Внутри вашего файла page.tsx
вставьте следующий код, а затем потратьте некоторое время на его изучение:
/dashboard/invoices/create/page.tsx | |
---|---|
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 |
|
Ваша страница - это серверный компонент, который получает данные customers
и передает их компоненту <Form>
. Чтобы сэкономить время, мы уже создали компонент <Form>
для вас.
Перейдите к компоненту <Form>
, и вы увидите, что форма:
- Имеет один элемент
<select>
(выпадающий) со списком клиентов. - Имеет один элемент
<input>
для суммы сtype=«number»
. - Имеет два элемента
<input>
для статуса сtype=«radio»
. - Имеет одну кнопку с
type=«submit»
.
На http://localhost:3000/dashboard/invoices/create вы должны увидеть следующий пользовательский интерфейс:
2. Создание Server Action¶
Отлично, теперь давайте создадим серверное действие, которое будет вызываться при отправке формы.
Перейдите в директорию lib/
и создайте новый файл с именем actions.ts
. В верхней части этого файла добавьте директиву React use server:
/app/lib/actions.ts | |
---|---|
1 |
|
Добавив "use server"
, вы помечаете все экспортируемые функции в файле как Server Actions. Затем эти серверные функции можно импортировать и использовать в компонентах Client и Server. Все неиспользуемые функции, включенные в этот файл, будут автоматически удалены из конечного пакета приложения.
Вы также можете писать Server Actions непосредственно в компонентах сервера, добавляя "use server"
внутри действия. Но для этого курса мы сохраним их все в отдельном файле. Мы рекомендуем иметь отдельный файл для ваших действий.
В файле actions.ts
создайте новую асинхронную функцию, которая принимает formData
:
/app/lib/actions.ts | |
---|---|
1 2 3 |
|
Затем в компоненте <Form>
импортируйте действие createInvoice
из файла actions.ts
. Добавьте атрибут action
к элементу <form>
и вызовите действие createInvoice
.
/app/ui/invoices/create-form.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Полезно знать
В HTML вы передаете URL в атрибуте action
. Этот URL будет местом назначения, куда должны быть отправлены данные вашей формы (обычно это конечная точка API).
Однако в React атрибут action
считается специальным реквизитом - то есть React строит поверх него, чтобы позволить вызывать действия.
За кулисами Server Actions создают конечную точку API POST
. Вот почему при использовании Server Actions вам не нужно создавать конечные точки API вручную.
3. Извлечение данных из формы FormData¶
Вернувшись в файл actions.ts
, вам нужно будет извлечь значения из formData
, есть пара методов, которые вы можете использовать. Для этого примера воспользуемся методом .get(name)
.
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
Подсказка
Если вы работаете с формами, содержащими много полей, возможно, вам стоит подумать об использовании метода entries()
с методом JavaScript Object.fromEntries()
.
Чтобы убедиться, что все подключено правильно, попробуйте использовать форму. После отправки вы должны увидеть данные, которые вы только что ввели в форму, зарегистрированные в вашем терминале (не в браузере).
Теперь, когда ваши данные имеют форму объекта, с ними будет гораздо проще работать.
4. Проверка и подготовка данных¶
Перед отправкой данных из формы в базу данных необходимо убедиться, что они имеют правильный формат и типы. Если вы помните из предыдущего курса, таблица счетов-фактур ожидает данные в следующем формате:
/app/lib/definitions.ts | |
---|---|
1 2 3 4 5 6 7 |
|
Пока у вас есть только customer_id
, amount
и status
из формы.
Валидация и принуждение типов
Важно проверить, чтобы данные из формы соответствовали ожидаемым типам в вашей базе данных. Например, если вы добавите console.log
внутри вашего действия:
1 |
|
Вы заметите, что amount
имеет тип string
, а не number
. Это потому, что элементы input
с type=«number»
на самом деле возвращают строку, а не число!
Чтобы справиться с проверкой типов, у вас есть несколько вариантов. Хотя вы можете проверять типы вручную, использование библиотеки проверки типов поможет вам сэкономить время и силы. В нашем примере мы будем использовать Zod, библиотеку проверки типов на основе TypeScript, которая может упростить вам эту задачу.
В файле actions.ts
импортируйте Zod и определите схему, соответствующую форме объекта формы. Эта схема будет проверять formData
перед сохранением в базу данных.
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Поле amount
специально настроено на коэрцитивную смену строки на число с одновременной проверкой его типа.
Затем вы можете передать ваши rawFormData
в CreateInvoice
для проверки типов:
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Хранение значений в центах
Обычно в базе данных принято хранить денежные значения в центах, чтобы исключить ошибки JavaScript с плавающей точкой и обеспечить большую точность.
Давайте переведем сумму в центы:
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Создание новых дат
Наконец, давайте создадим новую дату с форматом «ГГГГ-ММ-ДД» для даты создания счета-фактуры:
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
5. Вставка данных в базу данных¶
Теперь, когда у вас есть все необходимые значения для базы данных, вы можете создать SQL-запрос для вставки нового счета-фактуры в базу данных и передать переменные:
/app/lib/actions.ts | |
---|---|
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 |
|
Сейчас мы не обрабатываем никаких ошибок. Мы поговорим об этом в следующей главе. А пока перейдем к следующему шагу.
6. Ревалидация и перенаправление¶
Next.js имеет кэш маршрутизатора на стороне клиента, который хранит сегменты маршрута в браузере пользователя в течение некоторого времени. Вместе с prefetching этот кэш обеспечивает пользователям быстрый переход между маршрутами, сокращая при этом количество запросов к серверу.
Поскольку вы обновляете данные, отображаемые в маршруте «Счета-фактуры», вам нужно очистить этот кэш и вызвать новый запрос к серверу. Это можно сделать с помощью функции revalidatePath
из Next.js:
/app/lib/actions.ts | |
---|---|
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 |
|
После обновления базы данных путь /dashboard/invoices
будет перепроверен, и с сервера будут получены свежие данные.
В этот момент вы также хотите перенаправить пользователя обратно на страницу /dashboard/invoices
. Это можно сделать с помощью функции redirect
из Next.js:
/app/lib/actions.ts | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Поздравляем! Вы только что реализовали свое первое Server Actions. Протестируйте его, добавив новый счет-фактуру, если все работает правильно:
- При отправке вы должны быть перенаправлены на маршрут
/dashboard/invoices
. - Вы должны увидеть новый счет-фактуру в верхней части таблицы.
Обновление счета-фактуры¶
Форма обновления счета-фактуры аналогична форме создания счета-фактуры, за исключением того, что вам нужно будет передать id
счета-фактуры, чтобы обновить запись в вашей базе данных. Давайте посмотрим, как можно получить и передать id
счета-фактуры.
Вот шаги, которые необходимо выполнить для обновления счета-фактуры:
- Создайте новый сегмент динамического маршрута с
id
счета-фактуры. - Считайте
id
счета-фактуры из параметров страницы. - Получите конкретный счет-фактуру из базы данных.
- Предварительно заполните форму данными о счете.
- Обновите данные о счете-фактуре в своей базе данных.
1. Создайте сегмент динамического маршрута с идентификатором счета-фактуры¶
Next.js позволяет создавать Сегменты динамических маршрутов, когда вы не знаете точного названия сегмента и хотите создавать маршруты на основе данных. Это могут быть заголовки записей в блоге, страницы товаров и т. д. Вы можете создать динамические сегменты маршрутов, обернув имя папки в квадратные скобки. Например, [id]
, [post]
или [slug]
.
В папке /invoices
создайте новый динамический маршрут [id]
, затем новый маршрут edit
с файлом page.tsx
. Ваша файловая структура должна выглядеть следующим образом:
В компоненте <Table>
обратите внимание на кнопку <UpdateInvoice />
, которая получает id
счета-фактуры из записей таблицы.
/app/ui/invoices/table.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Перейдите к компоненту <UpdateInvoice />
и обновите href
в Link
, чтобы он принимал свойство id
. Вы можете использовать литералы шаблона для ссылки на динамический сегмент маршрута:
/app/ui/invoices/buttons.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
2. Считайте id
счета-фактуры из страницы params
¶
Вернитесь к компоненту <Page>
и вставьте следующий код:
/app/dashboard/invoices/[id]/edit/page.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Обратите внимание, что она похожа на страницу /create
счета-фактуры, только импортирует другую форму (из файла edit-form.tsx
). Эта форма должна быть предварительно заполнена со значением defaultValue
для имени клиента, суммы счета и статуса. Чтобы предварительно заполнить поля формы, необходимо получить конкретный счет-фактуру с помощью id
.
В дополнение к searchParams
, компоненты страницы также принимают параметр params
, который можно использовать для доступа к id
. Обновите свой компонент <Page>
, чтобы получить этот реквизит:
/app/dashboard/invoices/[id]/edit/page.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
3. Получите конкретный счет-фактуру¶
Затем:
- Импортируйте новую функцию
fetchInvoiceById
и передайтеid
в качестве аргумента. - Импортируйте функцию
fetchCustomers
, чтобы получить имена клиентов для выпадающего списка.
Вы можете использовать Promise.all
для параллельного получения данных о счетах и клиентах:
/dashboard/invoices/[id]/edit/page.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
В терминале вы увидите временную ошибку TypeScript для свойства invoice
, потому что invoice
может быть потенциально undefined
. Пока что не беспокойтесь об этом, вы решите эту проблему в следующей главе, когда добавите обработку ошибок.
Отлично! Теперь проверьте, что все подключено правильно. Зайдите на сайт http://localhost:3000/dashboard/invoices и нажмите на значок карандаша, чтобы отредактировать счет-фактуру. После навигации вы должны увидеть форму, в которую предварительно заносятся данные о счете:
URL также должен быть обновлен с id
следующим образом: http://localhost:3000/dashboard/invoice/uuid/edit
UUID против автоинкрементных ключей
Мы используем UUID вместо инкрементных ключей (например, 1, 2, 3 и т. д.). Это делает URL длиннее, однако UUID исключают риск столкновения идентификаторов, являются глобально уникальными и снижают риск атак перечисления, что делает их идеальными для больших баз данных.
Однако если вы предпочитаете более чистые URL-адреса, лучше использовать автоинкрементные ключи.
4. Передача id серверному действию¶
Наконец, вы хотите передать id
серверному действию, чтобы оно могло обновить нужную запись в вашей базе данных. Вы не можете передать id
в качестве аргумента, например:
/app/ui/invoices/edit-form.tsx | |
---|---|
1 2 |
|
Вместо этого вы можете передать id
серверному действию с помощью JS bind
. Это обеспечит кодировку любых значений, передаваемых серверному действию.
/app/ui/invoices/edit-form.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Замечание
Использование скрытого поля ввода в форме также работает (например, <input type="hidden" name="id" value={invoice.id} />
). Однако значения будут отображаться в виде полного текста в HTML-источнике, что не идеально для конфиденциальных данных.
Затем в файле actions.ts
создайте новое действие updateInvoice
:
/app/lib/actions.ts | |
---|---|
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 |
|
Аналогично действию createInvoice
, здесь вы:
- Извлекаем данные из
formData
. - Проверка типов с помощью Zod.
- Преобразование суммы в центы.
- Передача переменных в SQL-запрос.
- Вызов
revalidatePath
для очистки клиентского кэша и выполнения нового запроса к серверу. - Вызов
redirect
для перенаправления пользователя на страницу счета-фактуры.
Протестируйте его, отредактировав счет-фактуру. После отправки формы вы должны быть перенаправлены на страницу счета-фактуры, а счет-фактура должен быть обновлен.
Удаление счета-фактуры¶
Чтобы удалить счет-фактуру с помощью Server Actions, оберните кнопку удаления в элемент <form>
и передайте id
серверному действию с помощью bind
:
/app/ui/invoices/buttons.tsx | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Внутри файла actions.ts
создайте новое действие под названием deleteInvoice
.
/app/lib/actions.ts | |
---|---|
1 2 3 4 |
|
Поскольку это действие вызывается по пути /dashboard/invoices
, вам не нужно вызывать redirect
. Вызов revalidatePath
вызовет новый запрос к серверу и перерисует таблицу.
Дальнейшее чтение¶
В этой главе вы узнали, как использовать Server Actions для изменения данных. Вы также узнали, как использовать API revalidatePath
для повторной проверки кэша Next.js и redirect
для перенаправления пользователя на новую страницу.
Для получения дополнительных знаний вы также можете прочитать о безопасность Server Actions.
Источник — https://nextjs.org/learn/dashboard-app/mutating-data