В библиотеке Superstruct основная сила заключается не только в
готовых примитивах вроде string, number,
array и object, но и в возможности
конструировать собственные структуры, которые отражают доменную модель
приложения. Пользовательские структуры позволяют инкапсулировать правила
валидации, повторно использовать их и строить сложные схемы без
дублирования логики.
В основе любой структуры Superstruct лежит функция, которая принимает значение и возвращает либо успешный результат, либо ошибку валидации. Пользовательская структура по сути является обёрткой над такой функцией.
Создание собственной структуры начинается с функции
struct:
import { struct } fr om 'superstruct'
Функция позволяет определить новую структуру на основе уже существующих правил или полностью вручную.
Самый базовый вариант — обёртка над функцией проверки:
import { struct } from 'superstruct'
const PositiveNumber = struct((value) => {
return typeof value === 'number' && value > 0
})
Здесь создаётся структура, которая принимает только положительные числа.
Использование:
PositiveNumber(10) // 10
PositiveNumber(-5) // ошибка валидации
Такая форма полезна, когда требуется простая логика без дополнительных преобразований.
Пользовательские структуры часто строятся поверх встроенных примитивов:
import { struct, number } from 'superstruct'
const PositiveNumber = struct((value) => {
return number(value) && value > 0
})
Такой подход делает код более выразительным и уменьшает вероятность ошибок при проверке типа.
Один из ключевых инструментов для создания собственных структур —
refine. Он позволяет взять существующую структуру и
добавить к ней дополнительное условие.
import { number, refine } from 'superstruct'
const PositiveNumber = refine(number, 'PositiveNumber', (value) => {
return value > 0
})
Здесь:
number — базовая структура'PositiveNumber' — имя новой структурыТакой способ считается предпочтительным, так как сохраняет всю встроенную систему типизации и ошибок Superstruct.
Пользовательские структуры часто комбинируются с другими типами:
import { object, string, refine } from 'superstruct'
const Username = refine(string, 'Username', (value) => {
return value.length >= 3 && value.length <= 20
})
const User = object({
name: Username,
email: string(),
})
Здесь структура Username переиспользуется внутри объекта
User, формируя более сложную модель данных.
Некоторые пользовательские структуры не только проверяют, но и преобразуют данные.
import { struct } from 'superstruct'
const TrimmedString = struct((value) => {
if (typeof value !== 'string') return false
return value.trim()
})
Такая структура возвращает уже обработанное значение.
Более корректный вариант с явной логикой проверки и преобразования:
import { struct } from 'superstruct'
const TrimmedString = struct((value) => {
if (typeof value !== 'string') return false
const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : false
})
Одним из мощных подходов является создание функций, возвращающих структуры. Это позволяет параметризовать поведение.
import { refine, string } from 'superstruct'
const MinLengthString = (min) =>
refine(string, `MinLengthString(${min})`, (value) => {
return value.length >= min
})
Использование:
const Password = MinLengthString(8)
Такой подход позволяет строить гибкие правила без дублирования кода.
Фабрики структур становятся особенно полезны при построении повторно используемых ограничений.
const RangeNumber = (min, max) =>
refine(number, `RangeNumber(${min}, ${max})`, (value) => {
return value >= min && value <= max
})
Пример использования:
const Age = RangeNumber(0, 120)
const Rating = RangeNumber(1, 5)
Одна логика — разные ограничения.
Пользовательские структуры можно комбинировать в глубоко вложенные схемы:
import { object, array, string, number } from 'superstruct'
const Product = object({
title: string(),
price: number(),
})
const Catalog = object({
items: array(Product),
})
При необходимости можно подменять отдельные элементы на кастомные структуры:
const Price = refine(number, 'Price', (value) => value >= 0)
const Product = object({
title: string(),
price: Price,
})
Пользовательские структуры в Superstruct удобно выносить в отдельные модули:
// structures/username.js
import { refine, string } from 'superstruct'
export const Username = refine(string, 'Username', (value) => {
return value.length >= 3 && value.length <= 20
})
И использовать в разных частях приложения:
import { Username } from './structures/username'
Так формируется единая система правил валидации для всего проекта.
Создание пользовательских структур особенно полезно при моделировании предметной области:
const Email = refine(string, 'Email', (value) => {
return value.includes('@') && value.includes('.')
})
const UserRole = refine(string, 'UserRole', (value) => {
return ['admin', 'user', 'guest'].includes(value)
})
const User = object({
email: Email,
role: UserRole,
})
Такие структуры делают модель данных явной и независимой от бизнес-логики приложения.
Пользовательские структуры могут содержать другие кастомные структуры внутри себя, формируя многоуровневую систему проверок:
const PositiveInteger = refine(number, 'PositiveInteger', (v) => v > 0 && Number.isInteger(v))
const Pagination = object({
page: PositiveInteger,
lim it: PositiveInteger,
})
Каждый уровень инкапсулирует свою ответственность, не смешивая правила.
В зрелых кодовых базах пользовательские структуры становятся строительными блоками:
Email, Id,
DateString)PositivePrice,
AgeLimit)User, Order,
Product)Комбинирование этих уровней позволяет формировать строгую и предсказуемую модель данных без дублирования логики проверки.