https://habr.com/ru/post/540462/- Разработка веб-сайтов
- JavaScript
- Программирование
- ReactJS
Доброго времени суток, друзья!
В этом небольшом туториале я хочу продемонстировать вам пример клиент-серверной валидации формы.
Клиент будет реализован на React, сервер на Express.
Мы не будем изобретать велосипеды, а воспользуемся готовыми решениями: для валидации формы на стороне клиента будет использоваться
react-hook-form (+: используются хуки, русский язык), а на стороне сервера —
express-validator.
Для стилизации будет использоваться
styled-components (CSS-in-JS или All-in-JS, учитывая JSX).
Исходный код примера находится
здесь.
Поиграть с кодом можно
здесь.
Без дальнейших предисловий.
Клиент
Создаем проект с помощью
create-react-app:
yarn create react-app form-validation
# или
npm init react-app form-validation
# или
npx create-react-app form-validation
В дальнейшем для установки зависимостей и выполнения команд я буду использовать yarn.
Структура проекта после удаления лишних файлов:
public
index.html
src
App.js
index.js
styles.js
server.js
...
Устанавливаем зависимости:
# для клиента
yarn add styled-components react-hook-form
# для сервера (производственные зависимости)
yarn add express express-validator cors
# для сервера (зависимость для разработки)
yarn add -D nodemon
# для одновременного запуска серверов
yarn add concurrently
Поскольку styled-components не умеет импотировать шрифты, нам придется добавить их в public/index.html:
<head>
...
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Comfortaa&display=swap"
rel="stylesheet"
/>
</head>
Наша форма будет состоять из трех полей: имя пользователя, его адрес электронной почты и пароль. Условия, которым должны удовлетворять данные:
- Имя
- от 2 до 10 символов
- кириллица
- Email
- особых требований не предъявляется
- Пароль
- 8-12 символов
- латиница: буквы в любом регистре, цифры, нижнее подчеркивание и дефис
Начнем со стилизации (src/styles.js; для подстветки синтаксиса я использую расширение для VSCode — vscode-styled-components):
// импорт инструментов
import styled, { createGlobalStyle } from 'styled-components'
// глобальные стили
const GlobalStyle = createGlobalStyle`
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background-color: #1c1c1c;
font-family: 'Comfortaa', cursive;
font-size: 14px;
letter-spacing: 1px;
color: #f0f0f0;
}
`
// заголовок
const StyledTitle = styled.h1`
margin: 1em;
color: orange;
`
// форма
const StyledForm = styled.form`
margin: 0 auto;
width: 320px;
font-size: 1.2em;
text-align: center;
`
// подпись
const Label = styled.label`
margin: 0.5em;
display: grid;
grid-template-columns: 1fr 2fr;
align-items: center;
text-align: left;
`
// проект поля для ввода данных
const BaseInput = styled.input`
padding: 0.5em 0.75em;
font-family: inherit;
font-size: 0.9em;
letter-spacing: 1px;
outline: none;
border: none;
border-radius: 4px;
`
// обычное поле
const RegularInput = styled(BaseInput)`
background-color: #f0f0f0;
box-shadow: inset 0 0 2px orange;
&:focus {
background-color: #1c1c1c;
color: #f0f0f0;
box-shadow: inset 0 0 4px yellow;
}
`
// поле для отправки данных на сервер
const SubmitInput = styled(BaseInput)`
margin: 1em 0.5em;
background-image: linear-gradient(yellow, orange);
cursor: pointer;
&:active {
box-shadow: inset 0 1px 3px #1c1c1c;
}
`
// проект сообщения с текстом
const BaseText = styled.p`
font-size: 1.1em;
text-align: center;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
`
// сообщение об ошибке
const ErrorText = styled(BaseText)`
font-size: ${(props) => (props.small ? '0.8em' : '1.1em')};
color: red;
`
// сообщение об успехе
const SuccessText = styled(BaseText)`
color: green;
`
// экспорт стилизованных компонентов
export {
GlobalStyle,
StyledTitle,
StyledForm,
Label,
RegularInput,
SubmitInput,
ErrorText,
SuccessText
}
Импортируем и подключаем глобальные стили в src/index.js:
import React from 'react'
import ReactDOM from 'react-dom'
// импортируем глобальные стили
import { GlobalStyle } from './styles'
import App from './App'
ReactDOM.render(
<React.StrictMode>
{/* подключаем глобальные стили */}
<GlobalStyle />
<App />
</React.StrictMode>,
document.getElementById('root')
)
Переходим к основному файлу клиента (src/App.js):
import { useState } from 'react'
// импорт хука для валидации формы
import { useForm } from 'react-hook-form'
// импорт стилизованных компонентов
import {
StyledTitle,
StyledForm,
Label,
RegularInput,
SubmitInput,
ErrorText,
SuccessText
} from './styles'
// компонент заголовка
function Title() {
return <StyledTitle>Валидация формы</StyledTitle>
}
// компонент формы
function Form() {
// инициализируем начальное состояние
const [result, setResult] = useState({
message: '',
success: false
})
// извлекаем средства валидации:
// регистрация проверяемого поля
// ошибки и обработка отправки формы
const { register, errors, handleSubmit } = useForm()
// общие валидаторы
const validators = {
required: 'Не может быть пустым'
}
// функция отправки формы
async function onSubmit(values) {
console.log(values)
const response = await fetch('http://localhost:5000/server', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(values)
})
const result = await response.json()
// обновляем состояние
setResult({
message: result,
success: response.ok
})
}
// нажатие кнопки сброса полей в исходное состояние приводит к перезагрузке страницы
function onClick() {
window.location.reload()
}
return (
<>
<StyledForm onSubmit={handleSubmit(onSubmit)}>
<Label>
Имя:
<RegularInput
type='text'
name='name'
// поля являются неуправляемыми
// это повышает производительность
ref={register({
...validators,
minLength: {
value: 2,
message: 'Не менее двух букв'
},
maxLength: {
value: 10,
message: 'Не более десяти букв'
},
pattern: {
value: /[А-ЯЁ]{2,10}/i,
message: 'Только киррилица'
}
})}
defaultValue='Иван'
/>
</Label>
{/* ошибки */}
<ErrorText small>{errors.name && errors.name.message}</ErrorText>
<Label>
Email:
<RegularInput
type='email'
name='email'
ref={register({
...validators,
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Неправильный адрес электронной почты'
}
})}
defaultValue='email@example.com'
/>
</Label>
<ErrorText small>{errors.email && errors.email.message}</ErrorText>
<Label>
Пароль:
<RegularInput
type='password'
name='password'
ref={register({
...validators,
pattern: {
value: /^[A-Z0-9_-]{8,12}$/i,
message:
'От 8 до 12 символов: латиница, цифры, нижнее подчеркивание и дефис'
}
})}
defaultValue='password'
/>
</Label>
<ErrorText small>
{errors.password && errors.password.message}
</ErrorText>
<SubmitInput type='submit' defaultValue='Отправить' />
{/* обратите внимание на атрибут "as", он позволяет превратить "инпут" в кнопку с аналогичными стилями */}
<SubmitInput as='button' onClick={onClick}>
Сбросить
</SubmitInput>
</StyledForm>
{/* результат отправки формы */}
{result.success ? (
<SuccessText>{result.message}</SuccessText>
) : (
<ErrorText>{result.message}</ErrorText>
)}
</>
)
}
export default function App() {
return (
<>
<Title />
<Form />
</>
)
}
Метод register() хука useForm() поддерживает все атрибуты тега input.
Полный список таких атрибутов. В случае с именем, мы могли бы ограничиться регулярным выражением.
Запускаем сервер для клиента с помощью yarn start и тестируем форму:
Замечательно. Валидация на стороне клиента работает, как ожидается. Но ее всегда можно отключить. Поэтому нужна валидация на сервере.
Сервер
Приступаем к реализации сервера (server.js):
const express = require('express')
// body читает тело запроса
// validationResult - результат валидации
const { body, validationResult } = require('express-validator')
const cors = require('cors')
const app = express()
const PORT = process.env.PORT || 5000
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
// валидаторы
const validators = [
body('name').trim().notEmpty().isAlpha('ru-RU').escape(),
body('email').normalizeEmail().isEmail(),
// кастомный валидатор
body('password').custom((value) => {
const regex = /^[A-Z0-9_-]{8,12}$/i
if (!regex.test(value)) throw new Error('Пароль не соответствует шаблону')
return true
})
]
// валидаторы передаются в качестве middleware
app.post('/server', validators, (req, res) => {
// извлекаем массив с ошибками из результата валидации
const { errors } = validationResult(req)
console.log(errors)
// если массив с ошибками не является пустым
if (errors.length) {
res.status(400).json('Регистрация провалилась')
} else {
res.status(201).json('Регистрация прошла успешно')
}
})
app.listen(PORT, () => {
console.log(`Сервер готов. Порт: ${PORT}`)
})
Полный список доступных валидаторов можно посмотреть
здесь.
Добавим в package.json парочку скриптов — «server» для запуска сервера и «dev» для одновременного запуска серверов:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"server": "nodemon server",
"dev": "concurrently \"yarn server\" \"yarn start\""
}
Выполняем yarn dev и тестируем отправку формы:
Прекрасно. Кажется, у нас все получилось.
Мы с вами рассмотрели очень простой вариант клиент-серверной валидации формы. Вместе с тем, более сложные варианты предполагают лишь увеличение количества валидаторов, общие принципы остаются такими же. Также стоит отметить, что валидацию формы на стороне клиента вполне можно реализовать средствами HTML (
GitHub,
CodeSandbox).
Благодарю за внимание и хорошего дня.