@reformer/core 10.2.0 → 11.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -68
- package/dist/behaviors-value-D05z3my6.js +109 -0
- package/dist/behaviors.d.ts +1 -1
- package/dist/behaviors.js +376 -211
- package/dist/create-model-DPSG3I0O.js +30 -0
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/en-HP8XEych.js +44 -0
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +84 -0
- package/dist/form/behaviors/context.d.ts +56 -0
- package/dist/form/behaviors/index.d.ts +30 -0
- package/dist/form/behaviors/internals.d.ts +41 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
- package/dist/form/behaviors/operators.d.ts +59 -0
- package/dist/form/behaviors/schema-operators.d.ts +83 -0
- package/dist/form/behaviors/types.d.ts +37 -0
- package/dist/form/create-form.d.ts +59 -70
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +153 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +23 -27
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +159 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +27 -22
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +21 -91
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -91
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +148 -50
- package/dist/form/types/validation-schema.d.ts +24 -41
- package/dist/form/validation/context.d.ts +67 -0
- package/dist/form/validation/index.d.ts +24 -28
- package/dist/form/validation/operators.d.ts +101 -0
- package/dist/form/validation/run.d.ts +120 -0
- package/dist/form/validation/strategy.d.ts +89 -0
- package/dist/form/validation/types.d.ts +83 -0
- package/dist/form/validators/date-utils.d.ts +42 -0
- package/dist/form/validators/email.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
- package/dist/form/validators/future-date.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
- package/dist/form/validators/integer.d.ts +22 -0
- package/dist/form/validators/is-date.d.ts +24 -0
- package/dist/form/validators/is-number.d.ts +25 -0
- package/dist/form/validators/max-age.d.ts +28 -0
- package/dist/form/validators/max-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
- package/dist/form/validators/max-files.d.ts +26 -0
- package/dist/form/validators/max-length.d.ts +33 -0
- package/dist/form/validators/max-total-file-size.d.ts +26 -0
- package/dist/form/validators/max.d.ts +26 -0
- package/dist/form/validators/min-age.d.ts +27 -0
- package/dist/form/validators/min-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
- package/dist/form/validators/min-files.d.ts +27 -0
- package/dist/form/validators/min-length.d.ts +33 -0
- package/dist/form/validators/min.d.ts +26 -0
- package/dist/form/validators/multiple-of.d.ts +25 -0
- package/dist/form/validators/non-negative.d.ts +22 -0
- package/dist/form/validators/non-zero.d.ts +22 -0
- package/dist/form/validators/past-date.d.ts +23 -0
- package/dist/form/validators/pattern.d.ts +26 -0
- package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
- package/dist/form/validators/required.d.ts +29 -0
- package/dist/form/validators/url.d.ts +45 -0
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +49 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +46 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +136 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +49 -0
- package/dist/i18n/translator.d.ts +61 -0
- package/dist/i18n/validation-message.d.ts +53 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +86 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +932 -2779
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +45 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +44 -0
- package/dist/locale/ru.json +45 -0
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/{state → model}/behaviors-value.d.ts +24 -6
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
- package/dist/{state → model}/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +42 -0
- package/dist/model/model-nodes.d.ts +190 -0
- package/dist/model/model-signals-proxy.d.ts +63 -0
- package/dist/model/model-value-proxy.d.ts +84 -0
- package/dist/{state → model}/safe-effect.d.ts +50 -2
- package/dist/model/types.d.ts +239 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +27 -0
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +60 -38
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
- package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +18 -0
- package/dist/platforms/react/i18n/hooks.d.ts +65 -0
- package/dist/platforms/react/i18n/provider.d.ts +59 -0
- package/dist/platforms/react/index.d.ts +30 -0
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +22 -0
- package/dist/safe-effect-BazLOv1g.js +669 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-DzvnsWLh.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +11 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/future-date.js +10 -13
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-age.js +8 -12
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-date.js +10 -13
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max-length.js +7 -10
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-age.js +10 -11
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min-length.js +4 -7
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/multiple-of.js +11 -10
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-negative.js +2 -5
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/past-date.js +10 -13
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/phone.js +10 -11
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators/url.js +48 -17
- package/dist/validators.d.ts +1 -5
- package/dist/validators.js +98 -154
- package/docs/llms/01-api-reference.md +89 -0
- package/docs/llms/02-quick-start.md +181 -0
- package/docs/llms/03-api-signatures.md +494 -0
- package/docs/llms/04-common-patterns.md +204 -0
- package/docs/llms/05-common-mistakes.md +192 -0
- package/docs/llms/06-troubleshooting.md +16 -0
- package/docs/llms/07-complete-import.md +130 -0
- package/docs/llms/08-form-types.md +54 -0
- package/docs/llms/09-formschema.md +191 -0
- package/docs/llms/10-arrays.md +241 -0
- package/docs/llms/11-async-watchfield.md +53 -0
- package/docs/llms/12-array-cleanup.md +31 -0
- package/docs/llms/13-multi-step.md +162 -0
- package/docs/llms/14-extended-mistakes.md +101 -0
- package/docs/llms/15-project-structure.md +279 -0
- package/docs/llms/16-ui-components.md +229 -0
- package/docs/llms/17-nonexistent-api.md +278 -0
- package/docs/llms/18-conditional-fields.md +222 -0
- package/docs/llms/19-reading-values.md +100 -0
- package/docs/llms/20-compute-vs-watch.md +153 -0
- package/docs/llms/21-array-operations.md +163 -0
- package/docs/llms/22-cycle-detection.md +86 -0
- package/docs/llms/23-copy-from.md +141 -0
- package/docs/llms/24-sync-fields.md +115 -0
- package/docs/llms/25-reset-when.md +122 -0
- package/docs/llms/26-transform-value.md +136 -0
- package/docs/llms/27-revalidate-when.md +133 -0
- package/docs/llms/28-submit-and-reset.md +409 -0
- package/docs/llms/29-async-preload.md +203 -0
- package/docs/llms/30-type-safety-recipes.md +213 -0
- package/docs/llms/31-async-validator-debounce.md +178 -0
- package/docs/llms/32-async-options-loading.md +108 -0
- package/docs/llms/33-validation-strategy.md +305 -0
- package/docs/llms/34-i18n.md +298 -0
- package/docs/llms/35-unified-contract.md +309 -0
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +396 -0
- package/llms-index.json +1 -0
- package/llms.txt +10014 -3826
- package/package.json +77 -15
- package/dist/behaviors-node-DGEFMTQu.js +0 -18
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/error-handler.d.ts +0 -166
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -188
- package/dist/form/form-submitter.d.ts +0 -125
- package/dist/form/nodes/array-node.d.ts +0 -360
- package/dist/form/nodes/field-node.d.ts +0 -294
- package/dist/form/nodes/form-node.d.ts +0 -340
- package/dist/form/nodes/group-node.d.ts +0 -288
- package/dist/form/nodes/model-array-node.d.ts +0 -121
- package/dist/form/status-machine.d.ts +0 -153
- package/dist/form/types/deep-schema.d.ts +0 -116
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -53
- package/dist/form/validation/validators/date-utils.d.ts +0 -30
- package/dist/form/validation/validators/email.d.ts +0 -23
- package/dist/form/validation/validators/future-date.d.ts +0 -23
- package/dist/form/validation/validators/integer.d.ts +0 -22
- package/dist/form/validation/validators/is-date.d.ts +0 -23
- package/dist/form/validation/validators/is-number.d.ts +0 -24
- package/dist/form/validation/validators/max-age.d.ts +0 -25
- package/dist/form/validation/validators/max-date.d.ts +0 -25
- package/dist/form/validation/validators/max-files.d.ts +0 -26
- package/dist/form/validation/validators/max-length.d.ts +0 -27
- package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
- package/dist/form/validation/validators/max.d.ts +0 -25
- package/dist/form/validation/validators/min-age.d.ts +0 -25
- package/dist/form/validation/validators/min-date.d.ts +0 -25
- package/dist/form/validation/validators/min-files.d.ts +0 -26
- package/dist/form/validation/validators/min-length.d.ts +0 -27
- package/dist/form/validation/validators/min.d.ts +0 -25
- package/dist/form/validation/validators/multiple-of.d.ts +0 -24
- package/dist/form/validation/validators/non-negative.d.ts +0 -22
- package/dist/form/validation/validators/non-zero.d.ts +0 -22
- package/dist/form/validation/validators/past-date.d.ts +0 -23
- package/dist/form/validation/validators/pattern.d.ts +0 -33
- package/dist/form/validation/validators/required.d.ts +0 -32
- package/dist/form/validation/validators/url.d.ts +0 -43
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validation-strategy.d.ts +0 -55
- package/dist/form/validators.d.ts +0 -6
- package/dist/form-model-DYCzGGKF.js +0 -286
- package/dist/state/index.d.ts +0 -31
- package/dist/state/subscription-manager.d.ts +0 -180
- package/dist/state/types.d.ts +0 -144
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# revalidateWhen — Перевалидация по триггерам
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`revalidateWhen` вызывает переданный колбэк ревалидации, когда изменяется любая из
|
|
6
|
+
зависимостей. Применяется, когда правило зависит от значений других полей
|
|
7
|
+
(`amount <= maxAmount`, `confirmPassword === password`, `initialPayment >= propertyValue * 0.2`).
|
|
8
|
+
Валидация — отдельная функция-схема (`@reformer/core/validation`), прогоняемая по требованию через
|
|
9
|
+
`validateModel(model, schema)`. Поэтому ревалидация выражается явным колбэком, а не автоматической
|
|
10
|
+
привязкой к полю: поведение (`revalidateWhen`) лишь _инициирует_ прогон схемы валидации при изменении зависимости.
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
`revalidateWhen` — оператор поведения (`@reformer/core/behaviors`, либо примитив `@reformer/core/model`).
|
|
15
|
+
Его сигнатура НЕ зависит от контракта валидации:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// примитив: возвращает cleanup
|
|
19
|
+
function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): () => void;
|
|
20
|
+
|
|
21
|
+
// DSL: cleanup управляется формой
|
|
22
|
+
function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`deps` — массив сигналов-триггеров (`model.$.field`). `revalidate` — колбэк (обычно
|
|
26
|
+
`() => void validateModel(model, schema)`, где `schema` — `defineValidation<T>(({ model }) => …)`).
|
|
27
|
+
Вызывается при изменении любой зависимости (НЕ на инициализации), вне effect-контекста.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Базовый сценарий — перевалидация amount при смене maxAmount
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { defineFormBehavior, revalidateWhen } from '@reformer/core/behaviors';
|
|
35
|
+
import { validateModel } from '@reformer/core/validation';
|
|
36
|
+
|
|
37
|
+
export const paymentBehavior = defineFormBehavior<PaymentForm>(({ model }) => {
|
|
38
|
+
revalidateWhen([model.$.maxAmount], () => {
|
|
39
|
+
void validateModel(model, paymentSchema);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Пример: `revalidateWhen([model.$.maxAmount], () => void validateModel(model, paymentSchema))`.
|
|
45
|
+
Схема `paymentSchema` — стабильный module-level `const` (важно для отмены устаревших прогонов
|
|
46
|
+
в `validateModel`), где правило amount навешано оператором `cross` области схемы:
|
|
47
|
+
`cross(model.$.amount, amountVsMax)`.
|
|
48
|
+
|
|
49
|
+
### Несколько триггеров
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { defineFormBehavior, revalidateWhen } from '@reformer/core/behaviors';
|
|
53
|
+
import { validateModel } from '@reformer/core/validation';
|
|
54
|
+
|
|
55
|
+
export const mortgageBehavior = defineFormBehavior<MortgageForm>(({ model }) => {
|
|
56
|
+
// initialPayment зависит и от propertyValue, и от loanAmount
|
|
57
|
+
revalidateWhen([model.$.propertyValue, model.$.loanAmount], () => {
|
|
58
|
+
void validateModel(model, mortgageSchema);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Парная перевалидация — confirmPassword
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { defineFormBehavior, revalidateWhen } from '@reformer/core/behaviors';
|
|
67
|
+
import { validateModel } from '@reformer/core/validation';
|
|
68
|
+
|
|
69
|
+
// правило совпадения — cross-field в схеме: cross(model.$.confirmPassword, passwordsMatch),
|
|
70
|
+
// где passwordsMatch получает снимок формы (см. 03-api-signatures.md)
|
|
71
|
+
export const registrationBehavior = defineFormBehavior<RegistrationForm>(({ model }) => {
|
|
72
|
+
// при смене password перевалидируем схему (confirm перепроверится)
|
|
73
|
+
revalidateWhen([model.$.password], () => {
|
|
74
|
+
void validateModel(model, registrationSchema);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Как примитив (вне defineFormBehavior)
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { revalidateWhen } from '@reformer/core/model';
|
|
83
|
+
import { validateModel } from '@reformer/core/validation';
|
|
84
|
+
|
|
85
|
+
const stop = revalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Anti-patterns
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// ❌ Триггер == поле, которое и так меняется само
|
|
92
|
+
revalidateWhen([model.$.amount], () => validateModel(model, schema));
|
|
93
|
+
// amount и так валидируется при собственном изменении в общем прогоне
|
|
94
|
+
|
|
95
|
+
// ✅ Триггеры — ДРУГИЕ поля, от которых зависит правило amount
|
|
96
|
+
revalidateWhen([model.$.maxAmount, model.$.discount], () => validateModel(model, schema));
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// ❌ Правило только через revalidateWhen, но в схеме нет зависимости от триггера
|
|
101
|
+
revalidateWhen([model.$.maxAmount], () => validateModel(model, schema));
|
|
102
|
+
// а валидатор amount — статичный max(1000), не читает maxAmount
|
|
103
|
+
|
|
104
|
+
// ✅ Сначала cross-field правило (получает снимок формы) в схеме валидации, потом revalidateWhen
|
|
105
|
+
const amountVsMax = (form: Form): ValidationIssue | null =>
|
|
106
|
+
form.amount != null && form.maxAmount != null && form.amount > form.maxAmount
|
|
107
|
+
? { code: 'tooBig' }
|
|
108
|
+
: null;
|
|
109
|
+
|
|
110
|
+
const schema = defineValidation<Form>(({ model, cross }) => {
|
|
111
|
+
cross(model.$.amount, amountVsMax); // навешиваем правило на поле amount
|
|
112
|
+
});
|
|
113
|
+
revalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Troubleshooting
|
|
117
|
+
|
|
118
|
+
**Q: Ошибка target не пропадает после изменения триггера.**
|
|
119
|
+
A: Проверьте, что (1) правило реально читает значение триггера — cross-field `cross(sig, check)`,
|
|
120
|
+
где `check` получает снимок формы; (2) в `deps` передан именно сигнал (`model.$.trigger`);
|
|
121
|
+
(3) колбэк вызывает `validateModel` (он разносит ошибки по нодам формы).
|
|
122
|
+
|
|
123
|
+
**Q: Как перевалидировать только одно поле, а не всю схему?**
|
|
124
|
+
A: Схема — обычная функция, поэтому опиши стабильную под-схему только для этого поля и прогони её:
|
|
125
|
+
`validateModel(model, oneFieldSchema)`, где
|
|
126
|
+
`const oneFieldSchema = defineValidation<Form>(({ model, cross }) => cross(model.$.amount, amountVsMax))`.
|
|
127
|
+
Раннер выставит/погасит ошибки только затронутых полей (гашение накапливается на пару `(model, schema)`).
|
|
128
|
+
|
|
129
|
+
## See also
|
|
130
|
+
|
|
131
|
+
- [03-api-signatures.md](./03-api-signatures.md) — cross-field `cross` области схемы и раннер `validateModel`
|
|
132
|
+
- [28-submit-and-reset.md](./28-submit-and-reset.md) — полная валидация формы
|
|
133
|
+
- [20-compute-vs-watch.md](./20-compute-vs-watch.md) — реактивные производные значения
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Submit и Reset — Жизненный цикл отправки формы
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Канонический submit-флоу — один вызов отправки сборки: `bundle.submission.submit(handler)`.
|
|
6
|
+
Отправка — отдельный объект сборки (`FormSubmission`): она прогоняет все правила один раз,
|
|
7
|
+
разносит замечания по нодам и зовёт обработчик со значениями модели (`model.get()`) только при
|
|
8
|
+
статусе `valid`. Результат — объект `SubmitResult`, по которому отличают отправку (`submitted`),
|
|
9
|
+
замечания в форме (`invalid`), сбой проверки (`error`), отмену (`cancelled`) и исключение
|
|
10
|
+
обработчика (`failed`). После успеха — `model.reset()` (к initial-снимку) либо
|
|
11
|
+
`model.captureInitial()` (сохранённые значения — новая точка отсчёта).
|
|
12
|
+
|
|
13
|
+
Валидация — **отдельный слой** (`@reformer/core/validation`, схема через `defineValidation`): она
|
|
14
|
+
НЕ входит в схему формы и уходит в сборку полем `validation` (шаги — `flow`). Без сборки тот же
|
|
15
|
+
результат даёт раннер `validateModel(model, schema)`.
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Уровень формы — сборка createForm (см. 38-form-flow.md); отправка — объект bundle.submission
|
|
21
|
+
bundle.submission.submit<R>(handler: (values: T) => R | Promise<R>): Promise<SubmitResult<R>>;
|
|
22
|
+
type SubmitResult<R> =
|
|
23
|
+
| { status: 'submitted'; result: R } // обработчик отработал
|
|
24
|
+
| { status: 'invalid' | 'error' | 'cancelled'; validation: ValidationResult } // не вызывался
|
|
25
|
+
| { status: 'failed'; error: unknown }; // обработчик бросил исключение
|
|
26
|
+
bundle.submission.submitting: ReadonlySignal<boolean>; // работает обработчик
|
|
27
|
+
bundle.validate(): Promise<ValidationResult>; // проверить всю форму без отправки
|
|
28
|
+
bundle.validating: ReadonlySignal<boolean>; // идёт проверка
|
|
29
|
+
bundle.dispose(): void; // снять сборку: поведение, подписки нод, живую проверку; модель остаётся
|
|
30
|
+
|
|
31
|
+
// Отправка без сборки (визард в разметке без createForm) — без проверки
|
|
32
|
+
createSubmission<T>({ check: undefined, values: () => T }): FormSubmission<T>;
|
|
33
|
+
|
|
34
|
+
// Модель (источник истины значений):
|
|
35
|
+
interface ModelApi<T> {
|
|
36
|
+
get(): T; // снимок значений — его получает обработчик submit
|
|
37
|
+
set(value: T): void; // полная установка (все ключи T); при нехватке ключей — dev-предупреждение
|
|
38
|
+
patch(value: DeepPartial<T>): void; // частичное слияние: вложенные группы тоже частично, массив — целиком;
|
|
39
|
+
// ключ со значением undefined не трогается
|
|
40
|
+
isDirty(): boolean; // отличаются ли значения от initial-снимка (цели compute не в счёт)
|
|
41
|
+
reset(): void; // вернуть к initial-снимку (цели compute не пишет — их пересчитает поведение)
|
|
42
|
+
captureInitial(): void; // зафиксировать текущие как новый initial
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Раннер без сборки — headless: находит ноды по сигналам модели, разносит по ним замечания,
|
|
46
|
+
// отменяет устаревшие прогоны той же (model, schema):
|
|
47
|
+
validateModel<T>(model: FormModel<T>, schema: ValidationDefinition<T>, options?: { touch?: boolean }): Promise<boolean>
|
|
48
|
+
// true = статус 'valid': блокирующих замечаний нет (severity:'warning' не блокирует, но показывается).
|
|
49
|
+
// false = 'invalid', 'error' (правило не удалось проверить — на поле замечание ruleFailed) либо 'cancelled'.
|
|
50
|
+
runValidation<T>(model, schema): Promise<ValidationResult> // только сбор
|
|
51
|
+
applyValidationResult(result, options?: { touch?: boolean }): void // разнос по нодам
|
|
52
|
+
|
|
53
|
+
// Нода поля/формы (точечная работа с UI-состоянием):
|
|
54
|
+
form.markAsTouched(); // тронуть все поля (показать замечания)
|
|
55
|
+
form.clearIssues(); // снять замечания со всех нод
|
|
56
|
+
form.<field>.setIssues([{ code, params }]); // серверная/бизнес-ошибка в конкретное поле
|
|
57
|
+
form.<field>.clearIssues();
|
|
58
|
+
form.reset(); // к точке отсчёта модели + чистые ноды (замечания, touched, dirty)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Что отправка делает с полями
|
|
62
|
+
|
|
63
|
+
| Итог отправки | Касание (`touched`) |
|
|
64
|
+
| --------------------- | --------------------------------------------------------------------- |
|
|
65
|
+
| `invalid`, `error` | вся форма — видны замечания и полей, которых правила не касались |
|
|
66
|
+
| `cancelled` | форма тронутой не помечается: прогон устарел, замечаний итог не несёт |
|
|
67
|
+
| `submitted`, `failed` | только поля, которые проверили правила |
|
|
68
|
+
|
|
69
|
+
Отправка — попытка отправки: стратегия `afterFirstSubmit` после неё переходит в живую фазу
|
|
70
|
+
(`bundle.validate()` фазу не трогает, см. `33-validation-strategy.md`).
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
### Базовый submit-handler
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { createForm, type FormModel, type FormSchemaNode } from '@reformer/core';
|
|
78
|
+
import { useFormBundle } from '@reformer/core/react';
|
|
79
|
+
import { defineValidation, validate } from '@reformer/core/validation';
|
|
80
|
+
import { required, email, minLength } from '@reformer/core/validators';
|
|
81
|
+
|
|
82
|
+
interface RegistrationFormData {
|
|
83
|
+
username: string;
|
|
84
|
+
email: string;
|
|
85
|
+
password: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Валидация — отдельная схема над моделью; стабильная module-level `const`-ссылка.
|
|
89
|
+
const registrationRules = defineValidation<RegistrationFormData>(({ model }) => {
|
|
90
|
+
validate(model.$.username, [required({ code: 'username.required' }), minLength(3)]);
|
|
91
|
+
validate(model.$.email, [required({ code: 'email.required' }), email()]);
|
|
92
|
+
validate(model.$.password, [required({ code: 'password.required' }), minLength(8)]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const createRegistration = () =>
|
|
96
|
+
createForm<RegistrationFormData>({
|
|
97
|
+
initial: { username: '', email: '', password: '' },
|
|
98
|
+
schema: registrationSchema, // (model) => дерево узлов, без правил
|
|
99
|
+
validation: registrationRules,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
async function register(values: RegistrationFormData): Promise<Response> {
|
|
103
|
+
return fetch('/api/v1/auth/register', {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify(values),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function RegistrationForm() {
|
|
111
|
+
const registration = useFormBundle(createRegistration);
|
|
112
|
+
const { form, model } = registration;
|
|
113
|
+
|
|
114
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
// Один прогон правил: при замечаниях обработчик не вызывается, вся форма помечена тронутой.
|
|
117
|
+
const outcome = await registration.submission.submit(register);
|
|
118
|
+
if (outcome.status === 'submitted' && outcome.result.ok) model.reset(); // чистый старт
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<form onSubmit={handleSubmit}>
|
|
123
|
+
<FormField control={form.username} testId="username" />
|
|
124
|
+
<FormField control={form.email} testId="email" />
|
|
125
|
+
<FormField control={form.password} testId="password" />
|
|
126
|
+
<button type="submit">Отправить</button>
|
|
127
|
+
</form>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Submit с разбором результата и серверными замечаниями
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
async function handleSubmit(event: React.FormEvent) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
|
|
138
|
+
const outcome = await registration.submission.submit(async (values) => {
|
|
139
|
+
const response = await api.register(values);
|
|
140
|
+
if (!response.success) {
|
|
141
|
+
// Серверная бизнес-ошибка — НЕ сбрасываем форму, кладём замечание в поле
|
|
142
|
+
registration.form.username.setIssues([{ code: 'username.taken' }]);
|
|
143
|
+
}
|
|
144
|
+
return response;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
switch (outcome.status) {
|
|
148
|
+
case 'submitted':
|
|
149
|
+
if (outcome.result.success) {
|
|
150
|
+
registration.model.reset();
|
|
151
|
+
navigate('/welcome');
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case 'invalid': // замечания уже на полях
|
|
155
|
+
break;
|
|
156
|
+
case 'error': // async-правило не ответило — outcome.validation.failures
|
|
157
|
+
showToast('Не удалось проверить форму, повторите попытку');
|
|
158
|
+
break;
|
|
159
|
+
case 'cancelled': // форму правили, пока шла проверка, либо отправку сменила следующая
|
|
160
|
+
break; // замечаний нет — пользователь отправит ещё раз
|
|
161
|
+
case 'failed': // обработчик бросил исключение (сеть) — значения остаются
|
|
162
|
+
showToast(`Ошибка сети: ${String(outcome.error)}`);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Текст кода `username.taken` — запись в словаре формы (`defineIssueMessages`), см.
|
|
169
|
+
`36-issue-messages.md`.
|
|
170
|
+
|
|
171
|
+
### Значения для сервера
|
|
172
|
+
|
|
173
|
+
Обработчик получает `model.get()` — значения модели целиком: вычисляемые поля и поля, выключенные
|
|
174
|
+
`enableWhen` (их правила не считаются, но данные принадлежат модели). Поля, которых сервер не
|
|
175
|
+
ждёт, отрезает сам обработчик: удобно держать ввод и расчёт разными типами
|
|
176
|
+
(`CreditApplication` и `CreditApplicationDerived`, форма — их пересечение) и функцию
|
|
177
|
+
`toPayload(values): CreditApplication`, убирающую вычисляемые ключи.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const outcome = await credit.submission.submit((values) => api.send(toApplicationPayload(values)));
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Reset с подтверждением
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
function ActionButtons({ model }: { model: FormModel<RegistrationFormData> }) {
|
|
187
|
+
const handleReset = () => {
|
|
188
|
+
if (!model.isDirty()) return; // нечего сбрасывать
|
|
189
|
+
if (confirm('Очистить форму? Несохранённые изменения будут потеряны.')) {
|
|
190
|
+
model.reset();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const clearJustPassword = () => {
|
|
195
|
+
model.password = ''; // сброс одного поля — прямая запись в модель
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<>
|
|
200
|
+
<button type="button" onClick={handleReset}>Очистить</button>
|
|
201
|
+
<button type="button" onClick={clearJustPassword}>Очистить только пароль</button>
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Без сборки — раннер (server action / Node)
|
|
208
|
+
|
|
209
|
+
Раннер headless — работает без UI-компонентов, только с моделью и схемой:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { createModel } from '@reformer/core';
|
|
213
|
+
import { runValidation } from '@reformer/core/validation';
|
|
214
|
+
|
|
215
|
+
async function processFormPayload(rawData: Partial<MyForm>) {
|
|
216
|
+
const model = createModel<MyForm>(initialValues);
|
|
217
|
+
model.patch(rawData); // частичный load данных
|
|
218
|
+
|
|
219
|
+
// Полный результат: статус и замечания по ручкам полей — без нод формы
|
|
220
|
+
const result = await runValidation(model, myFormRules);
|
|
221
|
+
if (result.status !== 'valid') return { ok: false as const, status: result.status };
|
|
222
|
+
return { ok: true as const, data: model.get() };
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Снятие сборки — `bundle.dispose()`
|
|
227
|
+
|
|
228
|
+
Владелец сборки — тот, кто вызвал `createForm`. Вне React (тесты, сервер, своя оболочка) сборку
|
|
229
|
+
снимают, когда форма больше не нужна: `bundle.dispose()` снимает поведение формы, подписки нод и
|
|
230
|
+
живую проверку, а идущие проверки — полная, шага, отправки — получают `cancelled`, и их замечания
|
|
231
|
+
на ноды не ложатся. Значения модели остаются. Вызов идемпотентен.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const registration = createRegistration();
|
|
235
|
+
try {
|
|
236
|
+
const outcome = await registration.submission.submit(register);
|
|
237
|
+
expect(outcome.status).toBe('invalid');
|
|
238
|
+
} finally {
|
|
239
|
+
registration.dispose();
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- `level.dispose()` у `FormLevel` (уровень без сборки, `createFormLevel`) снимает только уровень —
|
|
244
|
+
живую проверку и идущие проверки; форму не трогает.
|
|
245
|
+
- Форму из `createFormFromModel` снимает `disposeForm(form)` — поведение и подписки нод.
|
|
246
|
+
- `useFormBundle` сборку при размонтировании не снимает.
|
|
247
|
+
|
|
248
|
+
## Anti-patterns
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// ❌ Отправка в обход проверки — правила не прогнаны, замечания не показаны
|
|
252
|
+
await api.send(bundle.model.get());
|
|
253
|
+
|
|
254
|
+
// ✅ Проверка и отправка одним вызовом
|
|
255
|
+
await bundle.submission.submit(api.send);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// ❌ Результат как boolean — объект всегда truthy
|
|
260
|
+
if (await bundle.submission.submit(api.send)) navigate('/done');
|
|
261
|
+
|
|
262
|
+
// ✅ Статус результата
|
|
263
|
+
const outcome = await bundle.submission.submit(api.send);
|
|
264
|
+
if (outcome.status === 'submitted') navigate('/done');
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// ❌ Методы проверки и отправки на ноде — удалены
|
|
269
|
+
await form.validate();
|
|
270
|
+
await form.submit(api.send);
|
|
271
|
+
|
|
272
|
+
// ✅ Проверка — у сборки, отправка — её объект submission (см. 17-nonexistent-api.md)
|
|
273
|
+
await bundle.validate();
|
|
274
|
+
await bundle.submission.submit(api.send);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// ❌ Чтение результата без await — сабмитим по неполному вердикту
|
|
279
|
+
const handleSubmit = (event) => {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
validateModel(model, schema); // Promise проигнорирован, async-правила не дождались
|
|
282
|
+
send(model.get());
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ✅ bundle.submission.submit дожидается async-правил сам
|
|
286
|
+
const handleSubmit = async (event) => {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
await bundle.submission.submit(send);
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// ❌ Правила в узле схемы формы — ключа validators у узла нет, схема формы правил не несёт
|
|
294
|
+
createForm({
|
|
295
|
+
initial,
|
|
296
|
+
schema: (model) => ({ children: [{ model: model.$.username, component: Input, validators: [required()] }] }),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ✅ Валидация — отдельная схема, уходит в сборку полем validation
|
|
300
|
+
const rules = defineValidation<T>(({ model }) => {
|
|
301
|
+
validate(model.$.username, [required()]);
|
|
302
|
+
});
|
|
303
|
+
createForm({ initial, schema, validation: rules });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// ❌ model.reset() до ответа сервера — потеря данных при ошибке
|
|
308
|
+
await api.send(model.get());
|
|
309
|
+
model.reset();
|
|
310
|
+
|
|
311
|
+
// ✅ Reset только после успеха — внутри обработчика либо по статусу 'submitted'
|
|
312
|
+
const outcome = await bundle.submission.submit(async (values) => {
|
|
313
|
+
await api.send(values);
|
|
314
|
+
bundle.model.reset();
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// ❌ Сборка в тесте или на сервере брошена — поведение и живая проверка продолжают работать
|
|
320
|
+
const bundle = createForm<RegistrationFormData>({ initial, schema, validation: rules });
|
|
321
|
+
await bundle.submission.submit(register);
|
|
322
|
+
|
|
323
|
+
// ✅ Владелец сборки снимает её сам; модель при этом остаётся
|
|
324
|
+
bundle.dispose();
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// ❌ Сброс полей по очереди — многословно
|
|
329
|
+
model.username = '';
|
|
330
|
+
model.email = '';
|
|
331
|
+
model.password = '';
|
|
332
|
+
|
|
333
|
+
// ✅ model.reset() — к initial-снимку одним вызовом
|
|
334
|
+
model.reset();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Troubleshooting
|
|
338
|
+
|
|
339
|
+
**Q: После `reset()` в UI остались старые замечания.**
|
|
340
|
+
A: `model.reset()` меняет значения; замечания в нодах чистит валидация. Перезапусти
|
|
341
|
+
`bundle.validate()` после reset (валидные поля погаснут сами), очисти напрямую —
|
|
342
|
+
`form.clearIssues()` / `form.<field>.clearIssues()`, либо сбрось ноды — `form.reset()`.
|
|
343
|
+
|
|
344
|
+
**Q: `submit` вызвал обработчик, но в поле висит замечание.**
|
|
345
|
+
A: Это правило с `severity: 'warning'` — оно показывается, но отправку не блокирует. Блокируют только
|
|
346
|
+
замечания без `severity` (по умолчанию `'error'`). Чтобы не пускать submit при warning — проверь
|
|
347
|
+
`(await bundle.validate()).issues` сам до отправки.
|
|
348
|
+
|
|
349
|
+
**Q: `submit` вернул `status: 'error'`, а замечаний правил в форме нет — на поле «Не удалось проверить поле».**
|
|
350
|
+
A: Async-правило не вернуло результат (сеть, исключение). Сбой блокирует: на поле — замечание
|
|
351
|
+
`{ code: 'ruleFailed' }`. Причина — в `outcome.validation.failures`. Повторная отправка после
|
|
352
|
+
восстановления сети снимет замечание.
|
|
353
|
+
|
|
354
|
+
**Q: `submit` вернул `status: 'cancelled'`, обработчик не вызван, замечаний нет.**
|
|
355
|
+
A: Проверку отправки сменила следующая (вторая отправка, живой прогон стратегии `change`) либо
|
|
356
|
+
модель изменилась, пока проверка шла. Пока отвечает async-правило, форма не заперта
|
|
357
|
+
(`submission.submitting` ещё `false`): пользователь поправил поле, пришла загрузка, поведение записало
|
|
358
|
+
значение после таймера или запроса — правила этих значений не видели, и отправлять их нельзя.
|
|
359
|
+
Форма тронутой не помечается. Отправку повторяют: следующий прогон проверит уже новые значения.
|
|
360
|
+
Отменой кончается и отправка, идущая в момент `bundle.dispose()`. Чтобы правок не было — запри
|
|
361
|
+
форму на время проверки по `bundle.validating`. Вызов в одном тике со сборкой формы или правкой
|
|
362
|
+
поля отправку не отменяет: проверка сначала дожидается отложенных записей поведения (колбэк
|
|
363
|
+
`onChange`, сброс `resetOnDisable`), и правила, и обработчик видят значения после них.
|
|
364
|
+
|
|
365
|
+
**Q: В тесте запрос async-правила не уходит сразу после `validate()` / `submit()`.**
|
|
366
|
+
A: Перед вызовом в том же тике правили модель, и поведение ещё не дописало свои отложенные
|
|
367
|
+
записи — проверка ждёт их, правила стартуют микротаском позже (`validating` поднят сразу).
|
|
368
|
+
Дождись записей поведения до вызова (`await Promise.resolve()` / свой `settle()`) либо читай
|
|
369
|
+
запрос правила после `await`. Без отложенных записей правила исполняются синхронно в момент
|
|
370
|
+
вызова.
|
|
371
|
+
|
|
372
|
+
**Q: `reset()` не возвращает данные, загруженные с сервера.**
|
|
373
|
+
A: `reset()` возвращает к initial-снимку (значения на момент `createModel`). У модели, которую
|
|
374
|
+
`createForm` создал из `initial`, снимок — собранная форма: в него входят правки `seed` и первые
|
|
375
|
+
записи поведения (копия `syncFields`, нормализация `transformValue`) — сборка выполняет их до
|
|
376
|
+
возврата, поэтому пара `syncFields` сбрасывается согласованной. `set/patch` НЕ меняют initial. Чтобы сделать
|
|
377
|
+
загруженные данные новой «точкой отсчёта» — вызови `model.captureInitial()` после загрузки.
|
|
378
|
+
|
|
379
|
+
**Q: Что сброс и `isDirty()` делают с вычисляемыми полями?**
|
|
380
|
+
A: Ничего. Цели `compute` / `computeFrom` и поля с `markDerived` (например, номер заявки от
|
|
381
|
+
сервера) `model.reset()` и `form.reset()` не пишут — так же, как `set` и `patch`: значение
|
|
382
|
+
пересчитает поведение, когда сброс вернёт его источники. В `isDirty()` такие поля не входят,
|
|
383
|
+
поэтому форма без единой правки не считается изменённой, даже если поведение уже посчитало ставку.
|
|
384
|
+
Явная запись — `model.rate = 0` или `form.rate.reset(0)` — пишет как обычно. Исключение —
|
|
385
|
+
`enableWhen(…, { resetOnDisable: true })`: выключенное поле возвращается к значению создания, даже
|
|
386
|
+
если оно цель `compute` (её `compute` обязан нести то же условие `when`).
|
|
387
|
+
|
|
388
|
+
**Q: Чем `form.reset()` отличается от `model.reset()`?**
|
|
389
|
+
A: Точка отсчёта у них одна — initial-снимок модели либо последний `model.captureInitial()`
|
|
390
|
+
(загруженные данные). `form.reset()` — сброс для пользователя (кнопка «Сбросить»): значения к точке
|
|
391
|
+
отсчёта и чистые ноды — замечания, touched, dirty; после него и форма, и модель не изменены.
|
|
392
|
+
`model.reset()` — сброс значений модели: ноды не трогает, замечания погаснут при следующей
|
|
393
|
+
проверке. Исключение — `enableWhen({ resetOnDisable: true })`: выключенное поле очищается к
|
|
394
|
+
значению, с которым нода создана, а не к загруженному.
|
|
395
|
+
|
|
396
|
+
**Q: Disabled-поля участвуют в submit-данных?**
|
|
397
|
+
A: Да. Обработчик получает `model.get()` — модель целиком, с выключенными полями: их правила не
|
|
398
|
+
считаются, но данные принадлежат модели. Фильтруй сам либо используй
|
|
399
|
+
`enableWhen({ resetOnDisable: true })`, чтобы при disable поле возвращалось к initial. Замечания
|
|
400
|
+
отключённых полей сняты, в проверку они не входят.
|
|
401
|
+
|
|
402
|
+
## See also
|
|
403
|
+
|
|
404
|
+
- [38-form-flow.md](./38-form-flow.md) — уровень формы: `validate`, `validateStep`, `submission`, поток шагов
|
|
405
|
+
- [29-async-preload.md](./29-async-preload.md) — initial values и preload через `set`/`patch`
|
|
406
|
+
- [13-multi-step.md](./13-multi-step.md) — пошаговая форма: поток `flow`, визард
|
|
407
|
+
- [27-revalidate-when.md](./27-revalidate-when.md) — мост «поведение → валидация»: `revalidateWhen([deps], () => void validateModel(model, schema))`
|
|
408
|
+
- [36-issue-messages.md](./36-issue-messages.md) — тексты замечаний по коду
|
|
409
|
+
- [05-common-mistakes.md](./05-common-mistakes.md) — типичные ошибки
|