#
tokens: 48246/50000 8/1152 files (page 22/35)
lines: off (toggle) GitHub
raw markdown copy
This is page 22 of 35. Use http://codebase.md/alibaba/formily?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .all-contributorsrc
├── .codecov.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows
│       ├── check-pr-title.yml
│       ├── ci.yml
│       ├── commitlint.yml
│       ├── issue-open-check.yml
│       ├── package-size.yml
│       └── pr-welcome.yml
├── .gitignore
├── .prettierrc.js
├── .umirc.js
├── .vscode
│   └── cspell.json
├── .yarnrc
├── CHANGELOG.md
├── commitlint.config.js
├── devtools
│   ├── .eslintrc
│   └── chrome-extension
│       ├── .npmignore
│       ├── assets
│       │   └── img
│       │       ├── loading.svg
│       │       └── logo
│       │           ├── 128x128.png
│       │           ├── 16x16.png
│       │           ├── 38x38.png
│       │           ├── 48x48.png
│       │           ├── error.png
│       │           ├── gray.png
│       │           └── scalable.png
│       ├── config
│       │   ├── webpack.base.ts
│       │   ├── webpack.dev.ts
│       │   └── webpack.prod.ts
│       ├── LICENSE.md
│       ├── package.json
│       ├── src
│       │   ├── app
│       │   │   ├── components
│       │   │   │   ├── FieldTree.tsx
│       │   │   │   ├── filter.ts
│       │   │   │   ├── LeftPanel.tsx
│       │   │   │   ├── RightPanel.tsx
│       │   │   │   ├── SearchBox.tsx
│       │   │   │   └── Tabs.tsx
│       │   │   ├── demo.tsx
│       │   │   └── index.tsx
│       │   └── extension
│       │       ├── backend.ts
│       │       ├── background.ts
│       │       ├── content.ts
│       │       ├── devpanel.tsx
│       │       ├── devtools.tsx
│       │       ├── inject.ts
│       │       ├── manifest.json
│       │       ├── popup.tsx
│       │       └── views
│       │           ├── devpanel.ejs
│       │           ├── devtools.ejs
│       │           └── popup.ejs
│       ├── tsconfig.build.json
│       └── tsconfig.json
├── docs
│   ├── functions
│   │   ├── contributors.ts
│   │   └── npm-search.ts
│   ├── guide
│   │   ├── advanced
│   │   │   ├── async.md
│   │   │   ├── async.zh-CN.md
│   │   │   ├── build.md
│   │   │   ├── build.zh-CN.md
│   │   │   ├── business-logic.md
│   │   │   ├── business-logic.zh-CN.md
│   │   │   ├── calculator.md
│   │   │   ├── calculator.zh-CN.md
│   │   │   ├── controlled.md
│   │   │   ├── controlled.zh-CN.md
│   │   │   ├── custom.md
│   │   │   ├── custom.zh-CN.md
│   │   │   ├── destructor.md
│   │   │   ├── destructor.zh-CN.md
│   │   │   ├── input.less
│   │   │   ├── layout.md
│   │   │   ├── layout.zh-CN.md
│   │   │   ├── linkages.md
│   │   │   ├── linkages.zh-CN.md
│   │   │   ├── validate.md
│   │   │   └── validate.zh-CN.md
│   │   ├── contribution.md
│   │   ├── contribution.zh-CN.md
│   │   ├── form-builder.md
│   │   ├── form-builder.zh-CN.md
│   │   ├── index.md
│   │   ├── index.zh-CN.md
│   │   ├── issue-helper.md
│   │   ├── issue-helper.zh-CN.md
│   │   ├── learn-formily.md
│   │   ├── learn-formily.zh-CN.md
│   │   ├── quick-start.md
│   │   ├── quick-start.zh-CN.md
│   │   ├── scenes
│   │   │   ├── dialog-drawer.md
│   │   │   ├── dialog-drawer.zh-CN.md
│   │   │   ├── edit-detail.md
│   │   │   ├── edit-detail.zh-CN.md
│   │   │   ├── index.less
│   │   │   ├── login-register.md
│   │   │   ├── login-register.zh-CN.md
│   │   │   ├── more.md
│   │   │   ├── more.zh-CN.md
│   │   │   ├── query-list.md
│   │   │   ├── query-list.zh-CN.md
│   │   │   ├── step-form.md
│   │   │   ├── step-form.zh-CN.md
│   │   │   ├── tab-form.md
│   │   │   ├── tab-form.zh-CN.md
│   │   │   └── VerifyCode.tsx
│   │   ├── upgrade.md
│   │   └── upgrade.zh-CN.md
│   ├── index.md
│   ├── index.zh-CN.md
│   └── site
│       ├── Contributors.less
│       ├── Contributors.tsx
│       ├── QrCode.less
│       ├── QrCode.tsx
│       ├── Section.less
│       ├── Section.tsx
│       └── styles.less
├── global.config.ts
├── jest.config.js
├── lerna.json
├── LICENSE.md
├── package.json
├── packages
│   ├── .eslintrc
│   ├── antd
│   │   ├── __tests__
│   │   │   ├── moment.spec.ts
│   │   │   └── sideEffects.spec.ts
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── build-style.ts
│   │   ├── create-style.ts
│   │   ├── docs
│   │   │   ├── components
│   │   │   │   ├── ArrayCards.md
│   │   │   │   ├── ArrayCards.zh-CN.md
│   │   │   │   ├── ArrayCollapse.md
│   │   │   │   ├── ArrayCollapse.zh-CN.md
│   │   │   │   ├── ArrayItems.md
│   │   │   │   ├── ArrayItems.zh-CN.md
│   │   │   │   ├── ArrayTable.md
│   │   │   │   ├── ArrayTable.zh-CN.md
│   │   │   │   ├── ArrayTabs.md
│   │   │   │   ├── ArrayTabs.zh-CN.md
│   │   │   │   ├── Cascader.md
│   │   │   │   ├── Cascader.zh-CN.md
│   │   │   │   ├── Checkbox.md
│   │   │   │   ├── Checkbox.zh-CN.md
│   │   │   │   ├── DatePicker.md
│   │   │   │   ├── DatePicker.zh-CN.md
│   │   │   │   ├── Editable.md
│   │   │   │   ├── Editable.zh-CN.md
│   │   │   │   ├── Form.md
│   │   │   │   ├── Form.zh-CN.md
│   │   │   │   ├── FormButtonGroup.md
│   │   │   │   ├── FormButtonGroup.zh-CN.md
│   │   │   │   ├── FormCollapse.md
│   │   │   │   ├── FormCollapse.zh-CN.md
│   │   │   │   ├── FormDialog.md
│   │   │   │   ├── FormDialog.zh-CN.md
│   │   │   │   ├── FormDrawer.md
│   │   │   │   ├── FormDrawer.zh-CN.md
│   │   │   │   ├── FormGrid.md
│   │   │   │   ├── FormGrid.zh-CN.md
│   │   │   │   ├── FormItem.md
│   │   │   │   ├── FormItem.zh-CN.md
│   │   │   │   ├── FormLayout.md
│   │   │   │   ├── FormLayout.zh-CN.md
│   │   │   │   ├── FormStep.md
│   │   │   │   ├── FormStep.zh-CN.md
│   │   │   │   ├── FormTab.md
│   │   │   │   ├── FormTab.zh-CN.md
│   │   │   │   ├── index.md
│   │   │   │   ├── index.zh-CN.md
│   │   │   │   ├── Input.md
│   │   │   │   ├── Input.zh-CN.md
│   │   │   │   ├── NumberPicker.md
│   │   │   │   ├── NumberPicker.zh-CN.md
│   │   │   │   ├── Password.md
│   │   │   │   ├── Password.zh-CN.md
│   │   │   │   ├── PreviewText.md
│   │   │   │   ├── PreviewText.zh-CN.md
│   │   │   │   ├── Radio.md
│   │   │   │   ├── Radio.zh-CN.md
│   │   │   │   ├── Reset.md
│   │   │   │   ├── Reset.zh-CN.md
│   │   │   │   ├── Select.md
│   │   │   │   ├── Select.zh-CN.md
│   │   │   │   ├── SelectTable.md
│   │   │   │   ├── SelectTable.zh-CN.md
│   │   │   │   ├── Space.md
│   │   │   │   ├── Space.zh-CN.md
│   │   │   │   ├── Submit.md
│   │   │   │   ├── Submit.zh-CN.md
│   │   │   │   ├── Switch.md
│   │   │   │   ├── Switch.zh-CN.md
│   │   │   │   ├── TimePicker.md
│   │   │   │   ├── TimePicker.zh-CN.md
│   │   │   │   ├── Transfer.md
│   │   │   │   ├── Transfer.zh-CN.md
│   │   │   │   ├── TreeSelect.md
│   │   │   │   ├── TreeSelect.zh-CN.md
│   │   │   │   ├── Upload.md
│   │   │   │   └── Upload.zh-CN.md
│   │   │   ├── index.md
│   │   │   └── index.zh-CN.md
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __builtins__
│   │   │   │   ├── hooks
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── useClickAway.ts
│   │   │   │   │   └── usePrefixCls.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── loading.ts
│   │   │   │   ├── moment.ts
│   │   │   │   ├── pickDataProps.ts
│   │   │   │   ├── portal.tsx
│   │   │   │   ├── render.ts
│   │   │   │   └── sort.tsx
│   │   │   ├── array-base
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── array-cards
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── array-collapse
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── array-items
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── array-table
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── array-tabs
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── cascader
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── checkbox
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── date-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── editable
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── form
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── form-button-group
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── form-collapse
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-dialog
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-drawer
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-grid
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── form-item
│   │   │   │   ├── animation.less
│   │   │   │   ├── grid.less
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── form-layout
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   ├── style.ts
│   │   │   │   └── useResponsiveFormLayout.ts
│   │   │   ├── form-step
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-tab
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── index.ts
│   │   │   ├── input
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── number-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── password
│   │   │   │   ├── index.tsx
│   │   │   │   ├── PasswordStrength.tsx
│   │   │   │   └── style.ts
│   │   │   ├── preview-text
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── radio
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   └── style.ts
│   │   │   ├── reset
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── select
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── select-table
│   │   │   │   ├── index.tsx
│   │   │   │   ├── style.less
│   │   │   │   ├── style.ts
│   │   │   │   ├── useCheckSlackly.tsx
│   │   │   │   ├── useFilterOptions.tsx
│   │   │   │   ├── useFlatOptions.tsx
│   │   │   │   ├── useSize.tsx
│   │   │   │   ├── useTitleAddon.tsx
│   │   │   │   └── utils.ts
│   │   │   ├── space
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── style.less
│   │   │   ├── style.ts
│   │   │   ├── submit
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── switch
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── time-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── transfer
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── tree-select
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   └── upload
│   │   │       ├── index.tsx
│   │   │       ├── placeholder.ts
│   │   │       └── style.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── benchmark
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   └── index.tsx
│   │   ├── template.ejs
│   │   ├── tsconfig.build.json
│   │   ├── tsconfig.json
│   │   ├── webpack.base.ts
│   │   ├── webpack.dev.ts
│   │   └── webpack.prod.ts
│   ├── core
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── docs
│   │   │   ├── api
│   │   │   │   ├── entry
│   │   │   │   │   ├── ActionResponse.less
│   │   │   │   │   ├── ActionResponse.tsx
│   │   │   │   │   ├── createForm.md
│   │   │   │   │   ├── createForm.zh-CN.md
│   │   │   │   │   ├── FieldEffectHooks.md
│   │   │   │   │   ├── FieldEffectHooks.zh-CN.md
│   │   │   │   │   ├── FormChecker.md
│   │   │   │   │   ├── FormChecker.zh-CN.md
│   │   │   │   │   ├── FormEffectHooks.md
│   │   │   │   │   ├── FormEffectHooks.zh-CN.md
│   │   │   │   │   ├── FormHooksAPI.md
│   │   │   │   │   ├── FormHooksAPI.zh-CN.md
│   │   │   │   │   ├── FormPath.md
│   │   │   │   │   ├── FormPath.zh-CN.md
│   │   │   │   │   ├── FormValidatorRegistry.md
│   │   │   │   │   └── FormValidatorRegistry.zh-CN.md
│   │   │   │   └── models
│   │   │   │       ├── ArrayField.md
│   │   │   │       ├── ArrayField.zh-CN.md
│   │   │   │       ├── Field.md
│   │   │   │       ├── Field.zh-CN.md
│   │   │   │       ├── Form.md
│   │   │   │       ├── Form.zh-CN.md
│   │   │   │       ├── ObjectField.md
│   │   │   │       ├── ObjectField.zh-CN.md
│   │   │   │       ├── Query.md
│   │   │   │       ├── Query.zh-CN.md
│   │   │   │       ├── VoidField.md
│   │   │   │       └── VoidField.zh-CN.md
│   │   │   ├── guide
│   │   │   │   ├── architecture.md
│   │   │   │   ├── architecture.zh-CN.md
│   │   │   │   ├── field.md
│   │   │   │   ├── field.zh-CN.md
│   │   │   │   ├── form.md
│   │   │   │   ├── form.zh-CN.md
│   │   │   │   ├── index.md
│   │   │   │   ├── index.zh-CN.md
│   │   │   │   ├── mvvm.md
│   │   │   │   ├── mvvm.zh-CN.md
│   │   │   │   ├── values.md
│   │   │   │   └── values.zh-CN.md
│   │   │   ├── index.md
│   │   │   └── index.zh-CN.md
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── array.spec.ts
│   │   │   │   ├── effects.spec.ts
│   │   │   │   ├── externals.spec.ts
│   │   │   │   ├── field.spec.ts
│   │   │   │   ├── form.spec.ts
│   │   │   │   ├── graph.spec.ts
│   │   │   │   ├── heart.spec.ts
│   │   │   │   ├── internals.spec.ts
│   │   │   │   ├── lifecycle.spec.ts
│   │   │   │   ├── object.spec.ts
│   │   │   │   ├── shared.ts
│   │   │   │   └── void.spec.ts
│   │   │   ├── effects
│   │   │   │   ├── index.ts
│   │   │   │   ├── onFieldEffects.ts
│   │   │   │   └── onFormEffects.ts
│   │   │   ├── global.d.ts
│   │   │   ├── index.ts
│   │   │   ├── models
│   │   │   │   ├── ArrayField.ts
│   │   │   │   ├── BaseField.ts
│   │   │   │   ├── Field.ts
│   │   │   │   ├── Form.ts
│   │   │   │   ├── Graph.ts
│   │   │   │   ├── Heart.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── LifeCycle.ts
│   │   │   │   ├── ObjectField.ts
│   │   │   │   ├── Query.ts
│   │   │   │   ├── types.ts
│   │   │   │   └── VoidField.ts
│   │   │   ├── shared
│   │   │   │   ├── checkers.ts
│   │   │   │   ├── constants.ts
│   │   │   │   ├── effective.ts
│   │   │   │   ├── externals.ts
│   │   │   │   └── internals.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── element
│   │   ├── .npmignore
│   │   ├── build-style.ts
│   │   ├── create-style.ts
│   │   ├── docs
│   │   │   ├── .vuepress
│   │   │   │   ├── components
│   │   │   │   │   ├── createCodeSandBox.js
│   │   │   │   │   ├── dumi-previewer.vue
│   │   │   │   │   └── highlight.js
│   │   │   │   ├── config.js
│   │   │   │   ├── enhanceApp.js
│   │   │   │   ├── styles
│   │   │   │   │   └── index.styl
│   │   │   │   └── util.js
│   │   │   ├── demos
│   │   │   │   ├── guide
│   │   │   │   │   ├── array-cards
│   │   │   │   │   │   ├── effects-json-schema.vue
│   │   │   │   │   │   ├── effects-markup-schema.vue
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── array-collapse
│   │   │   │   │   │   ├── effects-json-schema.vue
│   │   │   │   │   │   ├── effects-markup-schema.vue
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── array-items
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── array-table
│   │   │   │   │   │   ├── effects-json-schema.vue
│   │   │   │   │   │   ├── effects-markup-schema.vue
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── array-tabs
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── cascader
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── checkbox
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── date-picker
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── editable
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── form-button-group.vue
│   │   │   │   │   ├── form-collapse
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── form-dialog
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── form-drawer
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── form-grid
│   │   │   │   │   │   ├── form.vue
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── native.vue
│   │   │   │   │   ├── form-item
│   │   │   │   │   │   ├── bordered-none.vue
│   │   │   │   │   │   ├── common.vue
│   │   │   │   │   │   ├── feedback.vue
│   │   │   │   │   │   ├── inset.vue
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   ├── size.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── form-layout
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── form-step
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── form-tab
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   └── markup-schema.vue
│   │   │   │   │   ├── form.vue
│   │   │   │   │   ├── input
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── input-number
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── password
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── preview-text
│   │   │   │   │   │   ├── base.vue
│   │   │   │   │   │   └── extend.vue
│   │   │   │   │   ├── radio
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── reset
│   │   │   │   │   │   ├── base.vue
│   │   │   │   │   │   ├── force.vue
│   │   │   │   │   │   └── validate.vue
│   │   │   │   │   ├── select
│   │   │   │   │   │   ├── json-schema-async.vue
│   │   │   │   │   │   ├── json-schema-sync.vue
│   │   │   │   │   │   ├── markup-schema-async-search.vue
│   │   │   │   │   │   ├── markup-schema-async.vue
│   │   │   │   │   │   ├── markup-schema-sync.vue
│   │   │   │   │   │   ├── template-async.vue
│   │   │   │   │   │   └── template-sync.vue
│   │   │   │   │   ├── space
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── submit
│   │   │   │   │   │   ├── base.vue
│   │   │   │   │   │   └── loading.vue
│   │   │   │   │   ├── switch
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── time-picker
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   ├── transfer
│   │   │   │   │   │   ├── json-schema.vue
│   │   │   │   │   │   ├── markup-schema.vue
│   │   │   │   │   │   └── template.vue
│   │   │   │   │   └── upload
│   │   │   │   │       ├── json-schema.vue
│   │   │   │   │       ├── markup-schema.vue
│   │   │   │   │       └── template.vue
│   │   │   │   └── index.vue
│   │   │   ├── guide
│   │   │   │   ├── array-cards.md
│   │   │   │   ├── array-collapse.md
│   │   │   │   ├── array-items.md
│   │   │   │   ├── array-table.md
│   │   │   │   ├── array-tabs.md
│   │   │   │   ├── cascader.md
│   │   │   │   ├── checkbox.md
│   │   │   │   ├── date-picker.md
│   │   │   │   ├── editable.md
│   │   │   │   ├── form-button-group.md
│   │   │   │   ├── form-collapse.md
│   │   │   │   ├── form-dialog.md
│   │   │   │   ├── form-drawer.md
│   │   │   │   ├── form-grid.md
│   │   │   │   ├── form-item.md
│   │   │   │   ├── form-layout.md
│   │   │   │   ├── form-step.md
│   │   │   │   ├── form-tab.md
│   │   │   │   ├── form.md
│   │   │   │   ├── index.md
│   │   │   │   ├── input-number.md
│   │   │   │   ├── input.md
│   │   │   │   ├── password.md
│   │   │   │   ├── preview-text.md
│   │   │   │   ├── radio.md
│   │   │   │   ├── reset.md
│   │   │   │   ├── select.md
│   │   │   │   ├── space.md
│   │   │   │   ├── submit.md
│   │   │   │   ├── switch.md
│   │   │   │   ├── time-picker.md
│   │   │   │   ├── transfer.md
│   │   │   │   └── upload.md
│   │   │   └── README.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __builtins__
│   │   │   │   ├── configs
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── shared
│   │   │   │   │   ├── create-context.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── loading.ts
│   │   │   │   │   ├── portal.ts
│   │   │   │   │   ├── resolve-component.ts
│   │   │   │   │   ├── transform-component.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   └── styles
│   │   │   │       └── common.scss
│   │   │   ├── array-base
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-cards
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-collapse
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-items
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-table
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-tabs
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── cascader
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── checkbox
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── date-picker
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── editable
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── el-form
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── el-form-item
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── form
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-button-group
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-collapse
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-dialog
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── form-drawer
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-grid
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-item
│   │   │   │   ├── animation.scss
│   │   │   │   ├── grid.scss
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   ├── style.ts
│   │   │   │   └── var.scss
│   │   │   ├── form-layout
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   ├── style.ts
│   │   │   │   └── useResponsiveFormLayout.ts
│   │   │   ├── form-step
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── form-tab
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── index.ts
│   │   │   ├── input
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── input-number
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── password
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── preview-text
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── radio
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── reset
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── select
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── space
│   │   │   │   ├── index.ts
│   │   │   │   ├── style.scss
│   │   │   │   └── style.ts
│   │   │   ├── style.ts
│   │   │   ├── submit
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── switch
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── time-picker
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   ├── transfer
│   │   │   │   ├── index.ts
│   │   │   │   └── style.ts
│   │   │   └── upload
│   │   │       ├── index.ts
│   │   │       └── style.ts
│   │   ├── transformer.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── grid
│   │   ├── .npmignore
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── index.ts
│   │   │   └── observer.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── json-schema
│   │   ├── .npmignore
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── __snapshots__
│   │   │   │   │   └── schema.spec.ts.snap
│   │   │   │   ├── compiler.spec.ts
│   │   │   │   ├── patches.spec.ts
│   │   │   │   ├── schema.spec.ts
│   │   │   │   ├── server-validate.spec.ts
│   │   │   │   ├── shared.spec.ts
│   │   │   │   ├── transformer.spec.ts
│   │   │   │   └── traverse.spec.ts
│   │   │   ├── compiler.ts
│   │   │   ├── global.d.ts
│   │   │   ├── index.ts
│   │   │   ├── patches.ts
│   │   │   ├── polyfills
│   │   │   │   ├── index.ts
│   │   │   │   └── SPECIFICATION_1_0.ts
│   │   │   ├── schema.ts
│   │   │   ├── shared.ts
│   │   │   ├── transformer.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── next
│   │   ├── __tests__
│   │   │   ├── moment.spec.ts
│   │   │   └── sideEffects.spec.ts
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── build-style.ts
│   │   ├── create-style.ts
│   │   ├── docs
│   │   │   ├── components
│   │   │   │   ├── ArrayCards.md
│   │   │   │   ├── ArrayCards.zh-CN.md
│   │   │   │   ├── ArrayCollapse.md
│   │   │   │   ├── ArrayCollapse.zh-CN.md
│   │   │   │   ├── ArrayItems.md
│   │   │   │   ├── ArrayItems.zh-CN.md
│   │   │   │   ├── ArrayTable.md
│   │   │   │   ├── ArrayTable.zh-CN.md
│   │   │   │   ├── Cascader.md
│   │   │   │   ├── Cascader.zh-CN.md
│   │   │   │   ├── Checkbox.md
│   │   │   │   ├── Checkbox.zh-CN.md
│   │   │   │   ├── DatePicker.md
│   │   │   │   ├── DatePicker.zh-CN.md
│   │   │   │   ├── DatePicker2.md
│   │   │   │   ├── DatePicker2.zh-CN.md
│   │   │   │   ├── Editable.md
│   │   │   │   ├── Editable.zh-CN.md
│   │   │   │   ├── Form.md
│   │   │   │   ├── Form.zh-CN.md
│   │   │   │   ├── FormButtonGroup.md
│   │   │   │   ├── FormButtonGroup.zh-CN.md
│   │   │   │   ├── FormCollapse.md
│   │   │   │   ├── FormCollapse.zh-CN.md
│   │   │   │   ├── FormDialog.md
│   │   │   │   ├── FormDialog.zh-CN.md
│   │   │   │   ├── FormDrawer.md
│   │   │   │   ├── FormDrawer.zh-CN.md
│   │   │   │   ├── FormGrid.md
│   │   │   │   ├── FormGrid.zh-CN.md
│   │   │   │   ├── FormItem.md
│   │   │   │   ├── FormItem.zh-CN.md
│   │   │   │   ├── FormLayout.md
│   │   │   │   ├── FormLayout.zh-CN.md
│   │   │   │   ├── FormStep.md
│   │   │   │   ├── FormStep.zh-CN.md
│   │   │   │   ├── FormTab.md
│   │   │   │   ├── FormTab.zh-CN.md
│   │   │   │   ├── index.md
│   │   │   │   ├── index.zh-CN.md
│   │   │   │   ├── Input.md
│   │   │   │   ├── Input.zh-CN.md
│   │   │   │   ├── NumberPicker.md
│   │   │   │   ├── NumberPicker.zh-CN.md
│   │   │   │   ├── Password.md
│   │   │   │   ├── Password.zh-CN.md
│   │   │   │   ├── PreviewText.md
│   │   │   │   ├── PreviewText.zh-CN.md
│   │   │   │   ├── Radio.md
│   │   │   │   ├── Radio.zh-CN.md
│   │   │   │   ├── Reset.md
│   │   │   │   ├── Reset.zh-CN.md
│   │   │   │   ├── Select.md
│   │   │   │   ├── Select.zh-CN.md
│   │   │   │   ├── SelectTable.md
│   │   │   │   ├── SelectTable.zh-CN.md
│   │   │   │   ├── Space.md
│   │   │   │   ├── Space.zh-CN.md
│   │   │   │   ├── Submit.md
│   │   │   │   ├── Submit.zh-CN.md
│   │   │   │   ├── Switch.md
│   │   │   │   ├── Switch.zh-CN.md
│   │   │   │   ├── TimePicker.md
│   │   │   │   ├── TimePicker.zh-CN.md
│   │   │   │   ├── TimePicker2.md
│   │   │   │   ├── TimePicker2.zh-CN.md
│   │   │   │   ├── Transfer.md
│   │   │   │   ├── Transfer.zh-CN.md
│   │   │   │   ├── TreeSelect.md
│   │   │   │   ├── TreeSelect.zh-CN.md
│   │   │   │   ├── Upload.md
│   │   │   │   └── Upload.zh-CN.md
│   │   │   ├── index.md
│   │   │   └── index.zh-CN.md
│   │   ├── LESENCE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __builtins__
│   │   │   │   ├── empty.tsx
│   │   │   │   ├── hooks
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── useClickAway.ts
│   │   │   │   │   └── usePrefixCls.ts
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── index.ts
│   │   │   │   ├── loading.ts
│   │   │   │   ├── mapSize.ts
│   │   │   │   ├── mapStatus.ts
│   │   │   │   ├── moment.ts
│   │   │   │   ├── pickDataProps.ts
│   │   │   │   ├── portal.tsx
│   │   │   │   ├── render.ts
│   │   │   │   └── toArray.ts
│   │   │   ├── array-base
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-cards
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-collapse
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-items
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── array-table
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── cascader
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── checkbox
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── date-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── date-picker2
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── editable
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── form
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-button-group
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-collapse
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-dialog
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-drawer
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-grid
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-item
│   │   │   │   ├── animation.scss
│   │   │   │   ├── grid.scss
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   ├── scss
│   │   │   │   │   └── variable.scss
│   │   │   │   └── style.ts
│   │   │   ├── form-layout
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   ├── style.ts
│   │   │   │   └── useResponsiveFormLayout.ts
│   │   │   ├── form-step
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── form-tab
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── index.ts
│   │   │   ├── input
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── main.scss
│   │   │   ├── number-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── password
│   │   │   │   ├── index.tsx
│   │   │   │   ├── PasswordStrength.tsx
│   │   │   │   └── style.ts
│   │   │   ├── preview-text
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── radio
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── reset
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── select
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── select-table
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   ├── style.ts
│   │   │   │   ├── useCheckSlackly.tsx
│   │   │   │   ├── useFilterOptions.tsx
│   │   │   │   ├── useFlatOptions.tsx
│   │   │   │   ├── useSize.tsx
│   │   │   │   ├── useTitleAddon.tsx
│   │   │   │   └── utils.ts
│   │   │   ├── space
│   │   │   │   ├── index.tsx
│   │   │   │   ├── main.scss
│   │   │   │   └── style.ts
│   │   │   ├── style.ts
│   │   │   ├── submit
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── switch
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── time-picker
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── time-picker2
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── transfer
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   ├── tree-select
│   │   │   │   ├── index.tsx
│   │   │   │   └── style.ts
│   │   │   └── upload
│   │   │       ├── index.tsx
│   │   │       ├── main.scss
│   │   │       ├── placeholder.ts
│   │   │       └── style.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── path
│   │   ├── .npmignore
│   │   ├── benchmark.ts
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── accessor.spec.ts
│   │   │   │   ├── basic.spec.ts
│   │   │   │   ├── match.spec.ts
│   │   │   │   ├── parser.spec.ts
│   │   │   │   └── share.spec.ts
│   │   │   ├── contexts.ts
│   │   │   ├── destructor.ts
│   │   │   ├── index.ts
│   │   │   ├── matcher.ts
│   │   │   ├── parser.ts
│   │   │   ├── shared.ts
│   │   │   ├── tokenizer.ts
│   │   │   ├── tokens.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── react
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── docs
│   │   │   ├── api
│   │   │   │   ├── components
│   │   │   │   │   ├── ArrayField.md
│   │   │   │   │   ├── ArrayField.zh-CN.md
│   │   │   │   │   ├── ExpressionScope.md
│   │   │   │   │   ├── ExpressionScope.zh-CN.md
│   │   │   │   │   ├── Field.md
│   │   │   │   │   ├── Field.zh-CN.md
│   │   │   │   │   ├── FormConsumer.md
│   │   │   │   │   ├── FormConsumer.zh-CN.md
│   │   │   │   │   ├── FormProvider.md
│   │   │   │   │   ├── FormProvider.zh-CN.md
│   │   │   │   │   ├── ObjectField.md
│   │   │   │   │   ├── ObjectField.zh-CN.md
│   │   │   │   │   ├── RecordScope.md
│   │   │   │   │   ├── RecordScope.zh-CN.md
│   │   │   │   │   ├── RecordsScope.md
│   │   │   │   │   ├── RecordsScope.zh-CN.md
│   │   │   │   │   ├── RecursionField.md
│   │   │   │   │   ├── RecursionField.zh-CN.md
│   │   │   │   │   ├── SchemaField.md
│   │   │   │   │   ├── SchemaField.zh-CN.md
│   │   │   │   │   ├── VoidField.md
│   │   │   │   │   └── VoidField.zh-CN.md
│   │   │   │   ├── hooks
│   │   │   │   │   ├── useExpressionScope.md
│   │   │   │   │   ├── useExpressionScope.zh-CN.md
│   │   │   │   │   ├── useField.md
│   │   │   │   │   ├── useField.zh-CN.md
│   │   │   │   │   ├── useFieldSchema.md
│   │   │   │   │   ├── useFieldSchema.zh-CN.md
│   │   │   │   │   ├── useForm.md
│   │   │   │   │   ├── useForm.zh-CN.md
│   │   │   │   │   ├── useFormEffects.md
│   │   │   │   │   ├── useFormEffects.zh-CN.md
│   │   │   │   │   ├── useParentForm.md
│   │   │   │   │   └── useParentForm.zh-CN.md
│   │   │   │   └── shared
│   │   │   │       ├── connect.md
│   │   │   │       ├── connect.zh-CN.md
│   │   │   │       ├── context.md
│   │   │   │       ├── context.zh-CN.md
│   │   │   │       ├── mapProps.md
│   │   │   │       ├── mapProps.zh-CN.md
│   │   │   │       ├── mapReadPretty.md
│   │   │   │       ├── mapReadPretty.zh-CN.md
│   │   │   │       ├── observer.md
│   │   │   │       ├── observer.zh-CN.md
│   │   │   │       ├── Schema.md
│   │   │   │       └── Schema.zh-CN.md
│   │   │   ├── guide
│   │   │   │   ├── architecture.md
│   │   │   │   ├── architecture.zh-CN.md
│   │   │   │   ├── concept.md
│   │   │   │   ├── concept.zh-CN.md
│   │   │   │   ├── index.md
│   │   │   │   └── index.zh-CN.md
│   │   │   ├── index.md
│   │   │   └── index.zh-CN.md
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── expression.spec.tsx
│   │   │   │   ├── field.spec.tsx
│   │   │   │   ├── form.spec.tsx
│   │   │   │   ├── schema.json.spec.tsx
│   │   │   │   ├── schema.markup.spec.tsx
│   │   │   │   └── shared.tsx
│   │   │   ├── components
│   │   │   │   ├── ArrayField.tsx
│   │   │   │   ├── ExpressionScope.tsx
│   │   │   │   ├── Field.tsx
│   │   │   │   ├── FormConsumer.tsx
│   │   │   │   ├── FormProvider.tsx
│   │   │   │   ├── index.ts
│   │   │   │   ├── ObjectField.tsx
│   │   │   │   ├── ReactiveField.tsx
│   │   │   │   ├── RecordScope.tsx
│   │   │   │   ├── RecordsScope.tsx
│   │   │   │   ├── RecursionField.tsx
│   │   │   │   ├── SchemaField.tsx
│   │   │   │   └── VoidField.tsx
│   │   │   ├── global.d.ts
│   │   │   ├── hooks
│   │   │   │   ├── index.ts
│   │   │   │   ├── useAttach.ts
│   │   │   │   ├── useExpressionScope.ts
│   │   │   │   ├── useField.ts
│   │   │   │   ├── useFieldSchema.ts
│   │   │   │   ├── useForm.ts
│   │   │   │   ├── useFormEffects.ts
│   │   │   │   └── useParentForm.ts
│   │   │   ├── index.ts
│   │   │   ├── shared
│   │   │   │   ├── connect.ts
│   │   │   │   ├── context.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── render.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── reactive
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── benchmark.ts
│   │   ├── docs
│   │   │   ├── api
│   │   │   │   ├── action.md
│   │   │   │   ├── action.zh-CN.md
│   │   │   │   ├── autorun.md
│   │   │   │   ├── autorun.zh-CN.md
│   │   │   │   ├── batch.md
│   │   │   │   ├── batch.zh-CN.md
│   │   │   │   ├── define.md
│   │   │   │   ├── define.zh-CN.md
│   │   │   │   ├── hasCollected.md
│   │   │   │   ├── hasCollected.zh-CN.md
│   │   │   │   ├── markObservable.md
│   │   │   │   ├── markObservable.zh-CN.md
│   │   │   │   ├── markRaw.md
│   │   │   │   ├── markRaw.zh-CN.md
│   │   │   │   ├── model.md
│   │   │   │   ├── model.zh-CN.md
│   │   │   │   ├── observable.md
│   │   │   │   ├── observable.zh-CN.md
│   │   │   │   ├── observe.md
│   │   │   │   ├── observe.zh-CN.md
│   │   │   │   ├── raw.md
│   │   │   │   ├── raw.zh-CN.md
│   │   │   │   ├── react
│   │   │   │   │   ├── observer.md
│   │   │   │   │   └── observer.zh-CN.md
│   │   │   │   ├── reaction.md
│   │   │   │   ├── reaction.zh-CN.md
│   │   │   │   ├── toJS.md
│   │   │   │   ├── toJS.zh-CN.md
│   │   │   │   ├── tracker.md
│   │   │   │   ├── tracker.zh-CN.md
│   │   │   │   ├── typeChecker.md
│   │   │   │   ├── typeChecker.zh-CN.md
│   │   │   │   ├── untracked.md
│   │   │   │   ├── untracked.zh-CN.md
│   │   │   │   └── vue
│   │   │   │       ├── observer.md
│   │   │   │       └── observer.zh-CN.md
│   │   │   ├── guide
│   │   │   │   ├── best-practice.md
│   │   │   │   ├── best-practice.zh-CN.md
│   │   │   │   ├── concept.md
│   │   │   │   ├── concept.zh-CN.md
│   │   │   │   ├── index.md
│   │   │   │   └── index.zh-CN.md
│   │   │   ├── index.md
│   │   │   └── index.zh-CN.md
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── action.spec.ts
│   │   │   │   ├── annotations.spec.ts
│   │   │   │   ├── array.spec.ts
│   │   │   │   ├── autorun.spec.ts
│   │   │   │   ├── batch.spec.ts
│   │   │   │   ├── collections-map.spec.ts
│   │   │   │   ├── collections-set.spec.ts
│   │   │   │   ├── collections-weakmap.spec.ts
│   │   │   │   ├── collections-weakset.spec.ts
│   │   │   │   ├── define.spec.ts
│   │   │   │   ├── externals.spec.ts
│   │   │   │   ├── hasCollected.spec.ts
│   │   │   │   ├── observable.spec.ts
│   │   │   │   ├── observe.spec.ts
│   │   │   │   ├── tracker.spec.ts
│   │   │   │   └── untracked.spec.ts
│   │   │   ├── action.ts
│   │   │   ├── annotations
│   │   │   │   ├── box.ts
│   │   │   │   ├── computed.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── observable.ts
│   │   │   │   ├── ref.ts
│   │   │   │   └── shallow.ts
│   │   │   ├── array.ts
│   │   │   ├── autorun.ts
│   │   │   ├── batch.ts
│   │   │   ├── checkers.ts
│   │   │   ├── environment.ts
│   │   │   ├── externals.ts
│   │   │   ├── global.d.ts
│   │   │   ├── handlers.ts
│   │   │   ├── index.ts
│   │   │   ├── internals.ts
│   │   │   ├── model.ts
│   │   │   ├── observable.ts
│   │   │   ├── observe.ts
│   │   │   ├── reaction.ts
│   │   │   ├── tracker.ts
│   │   │   ├── tree.ts
│   │   │   ├── types.ts
│   │   │   └── untracked.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── reactive-react
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── hooks
│   │   │   │   ├── index.ts
│   │   │   │   ├── useCompatEffect.ts
│   │   │   │   ├── useCompatFactory.ts
│   │   │   │   ├── useDidUpdate.ts
│   │   │   │   ├── useForceUpdate.ts
│   │   │   │   ├── useLayoutEffect.ts
│   │   │   │   └── useObserver.ts
│   │   │   ├── index.ts
│   │   │   ├── observer.ts
│   │   │   ├── shared
│   │   │   │   ├── gc.ts
│   │   │   │   ├── global.ts
│   │   │   │   ├── immediate.ts
│   │   │   │   └── index.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── reactive-test-cases-for-react18
│   │   ├── .npmignore
│   │   ├── .umirc.js
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── index.js
│   │   │   └── MySlowList.js
│   │   ├── template.ejs
│   │   ├── tsconfig.build.json
│   │   ├── tsconfig.json
│   │   ├── webpack.base.ts
│   │   ├── webpack.dev.ts
│   │   └── webpack.prod.ts
│   ├── reactive-vue
│   │   ├── .npmignore
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   └── observer.spec.ts
│   │   │   ├── hooks
│   │   │   │   ├── index.ts
│   │   │   │   └── useObserver.ts
│   │   │   ├── index.ts
│   │   │   ├── observer
│   │   │   │   ├── collectData.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── observerInVue2.ts
│   │   │   │   └── observerInVue3.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── shared
│   │   ├── .npmignore
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   └── index.spec.ts
│   │   │   ├── array.ts
│   │   │   ├── case.ts
│   │   │   ├── checkers.ts
│   │   │   ├── clone.ts
│   │   │   ├── compare.ts
│   │   │   ├── defaults.ts
│   │   │   ├── deprecate.ts
│   │   │   ├── global.ts
│   │   │   ├── index.ts
│   │   │   ├── instanceof.ts
│   │   │   ├── isEmpty.ts
│   │   │   ├── merge.ts
│   │   │   ├── middleware.ts
│   │   │   ├── path.ts
│   │   │   ├── string.ts
│   │   │   ├── subscribable.ts
│   │   │   └── uid.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── validator
│   │   ├── .npmignore
│   │   ├── LICENSE.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── rollup.config.js
│   │   ├── src
│   │   │   ├── __tests__
│   │   │   │   ├── parser.spec.ts
│   │   │   │   ├── registry.spec.ts
│   │   │   │   └── validator.spec.ts
│   │   │   ├── formats.ts
│   │   │   ├── index.ts
│   │   │   ├── locale.ts
│   │   │   ├── parser.ts
│   │   │   ├── registry.ts
│   │   │   ├── rules.ts
│   │   │   ├── template.ts
│   │   │   ├── types.ts
│   │   │   └── validator.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   └── vue
│       ├── .npmignore
│       ├── bin
│       │   ├── formily-vue-fix.js
│       │   └── formily-vue-switch.js
│       ├── docs
│       │   ├── .vuepress
│       │   │   ├── components
│       │   │   │   ├── createCodeSandBox.js
│       │   │   │   ├── dumi-previewer.vue
│       │   │   │   └── highlight.js
│       │   │   ├── config.js
│       │   │   ├── enhanceApp.js
│       │   │   └── styles
│       │   │       └── index.styl
│       │   ├── api
│       │   │   ├── components
│       │   │   │   ├── array-field.md
│       │   │   │   ├── expression-scope.md
│       │   │   │   ├── field.md
│       │   │   │   ├── form-consumer.md
│       │   │   │   ├── form-provider.md
│       │   │   │   ├── object-field.md
│       │   │   │   ├── recursion-field-with-component.md
│       │   │   │   ├── recursion-field.md
│       │   │   │   ├── schema-field-with-schema.md
│       │   │   │   ├── schema-field.md
│       │   │   │   └── void-field.md
│       │   │   ├── hooks
│       │   │   │   ├── use-field-schema.md
│       │   │   │   ├── use-field.md
│       │   │   │   ├── use-form-effects.md
│       │   │   │   ├── use-form.md
│       │   │   │   └── use-parent-form.md
│       │   │   └── shared
│       │   │       ├── connect.md
│       │   │       ├── injections.md
│       │   │       ├── map-props.md
│       │   │       ├── map-read-pretty.md
│       │   │       ├── observer.md
│       │   │       └── schema.md
│       │   ├── demos
│       │   │   ├── api
│       │   │   │   ├── components
│       │   │   │   │   ├── array-field.vue
│       │   │   │   │   ├── expression-scope.vue
│       │   │   │   │   ├── field.vue
│       │   │   │   │   ├── form-consumer.vue
│       │   │   │   │   ├── form-provider.vue
│       │   │   │   │   ├── object-field.vue
│       │   │   │   │   ├── recursion-field-with-component.vue
│       │   │   │   │   ├── recursion-field.vue
│       │   │   │   │   ├── schema-field-with-schema.vue
│       │   │   │   │   ├── schema-field.vue
│       │   │   │   │   └── void-field.vue
│       │   │   │   ├── hooks
│       │   │   │   │   ├── use-field-schema.vue
│       │   │   │   │   ├── use-field.vue
│       │   │   │   │   ├── use-form-effects.vue
│       │   │   │   │   ├── use-form.vue
│       │   │   │   │   └── use-parent-form.vue
│       │   │   │   └── shared
│       │   │   │       ├── connect.vue
│       │   │   │       ├── map-props.vue
│       │   │   │       ├── map-read-pretty.vue
│       │   │   │       └── observer.vue
│       │   │   ├── index.vue
│       │   │   └── questions
│       │   │       ├── default-slot.vue
│       │   │       ├── events.vue
│       │   │       ├── named-slot.vue
│       │   │       └── scoped-slot.vue
│       │   ├── guide
│       │   │   ├── architecture.md
│       │   │   ├── concept.md
│       │   │   └── README.md
│       │   ├── questions
│       │   │   └── README.md
│       │   └── README.md
│       ├── package.json
│       ├── README.md
│       ├── rollup.config.js
│       ├── scripts
│       │   ├── postinstall.js
│       │   ├── switch-cli.js
│       │   └── utils.js
│       ├── src
│       │   ├── __tests__
│       │   │   ├── expression.scope.spec.ts
│       │   │   ├── field.spec.ts
│       │   │   ├── form.spec.ts
│       │   │   ├── schema.json.spec.ts
│       │   │   ├── schema.markup.spec.ts
│       │   │   ├── shared.spec.ts
│       │   │   └── utils.spec.ts
│       │   ├── components
│       │   │   ├── ArrayField.ts
│       │   │   ├── ExpressionScope.ts
│       │   │   ├── Field.ts
│       │   │   ├── FormConsumer.ts
│       │   │   ├── FormProvider.ts
│       │   │   ├── index.ts
│       │   │   ├── ObjectField.ts
│       │   │   ├── ReactiveField.ts
│       │   │   ├── RecursionField.ts
│       │   │   ├── SchemaField.ts
│       │   │   └── VoidField.ts
│       │   ├── global.d.ts
│       │   ├── hooks
│       │   │   ├── index.ts
│       │   │   ├── useAttach.ts
│       │   │   ├── useField.ts
│       │   │   ├── useFieldSchema.ts
│       │   │   ├── useForm.ts
│       │   │   ├── useFormEffects.ts
│       │   │   ├── useInjectionCleaner.ts
│       │   │   └── useParentForm.ts
│       │   ├── index.ts
│       │   ├── shared
│       │   │   ├── connect.ts
│       │   │   ├── context.ts
│       │   │   ├── createForm.ts
│       │   │   ├── fragment.ts
│       │   │   ├── h.ts
│       │   │   └── index.ts
│       │   ├── types
│       │   │   └── index.ts
│       │   ├── utils
│       │   │   ├── formatVNodeData.ts
│       │   │   ├── getFieldProps.ts
│       │   │   ├── getRawComponent.ts
│       │   │   └── resolveSchemaProps.ts
│       │   └── vue2-components.ts
│       ├── tsconfig.build.json
│       ├── tsconfig.json
│       └── tsconfig.types.json
├── README.md
├── README.zh-cn.md
├── scripts
│   ├── build-style
│   │   ├── buildAllStyles.ts
│   │   ├── copy.ts
│   │   ├── helper.ts
│   │   └── index.ts
│   └── rollup.base.js
├── tsconfig.build.json
├── tsconfig.jest.json
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/packages/core/docs/api/entry/FieldEffectHooks.md:
--------------------------------------------------------------------------------

```markdown
---
order: 2
---

# Field Effect Hooks

## onFieldInit

#### Description

Used to monitor the side effect hook of a field initialization, we will trigger the field initialization event when we call createField

#### Signature

```ts
interface onFieldInit {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

<Alert>
  For the syntax format of FormPathPattern, please refer to <a href="/api/entry/form-path">FormPath</a>
</Alert>

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldInit } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldInit('target', () => {
            setResponse('target has been initialized')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          form.createField({ name: 'target' })
        }}
      >
        Create field
      </button>
    </ActionResponse>
  )
}
```

## onFieldMount

#### Description

Used to monitor the side-effect hook of a field that has been mounted, we will trigger the field mount event when we call onMount

#### Signature

```ts
interface onFieldMount {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldMount } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldMount('target', () => {
            setResponse('target is mounted')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          form.createField({ name: 'target' }).onMount()
        }}
      >
        Create and mount fields
      </button>
    </ActionResponse>
  )
}
```

## onFieldUnmount

#### Description

It is used to monitor the side effect hook that a field has been unloaded. When we call onUnmount, the unmount event will be triggered

#### Signature

```ts
interface onFieldUnmount {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldMount, onFieldUnmount } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldMount('target', () => {
            setResponse('target is mounted')
          })
          onFieldUnmount('target', () => {
            setResponse('target has been uninstalled')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          form.createField({ name: 'target' }).onMount()
        }}
      >
        Create and mount fields
      </button>
      <button
        onClick={() => {
          form.createField({ name: 'target' }).onUnmount()
        }}
      >
        Unload field
      </button>
    </ActionResponse>
  )
}
```

## onFieldReact

A side-effect hook used to implement field reactive logic. Its core principle is that the callback function will be executed when the field is initialized, and the dependency will be automatically tracked at the same time. The callback function will be executed repeatedly when the dependent data changes.

#### Signature

```ts
interface onFieldReact {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldReact } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects(form) {
          onFieldReact('target', () => {
            setResponse(
              'target ' + (form.values.other === 123 ? 'display' : 'hide')
            )
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          form.createField({ name: 'target' })
        }}
      >
        Initialize target
      </button>
      <button
        onClick={() => {
          const field = form.createField({ name: 'other' })
          field.setValue(123)
        }}
      >
        Assign other = 123
      </button>
      <button
        onClick={() => {
          const field = form.createField({ name: 'other' })
          field.setValue(null)
        }}
      >
        Assign other = null
      </button>
    </ActionResponse>
  )
}
```

> This example will track the changes of values.other, if it is equal to 123, it will control the display of the target, otherwise it will be hidden

## onFieldChange

#### Description

Side effect hook used to monitor the property changes of a field

#### Signature

```ts
interface onFieldChange {
  (
    pattern: FormPathPattern,
    watches?: string[],
    callback: (field: Field, form: Form) => void
  )
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

You can pass in the specific set of attributes you want to monitor, or you can leave it alone, the default is to monitor value changes

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldChange } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldChange('target', (field) => {
            setResponse('target value change:' + field.value)
          })
          onFieldChange('target', ['component'], () => {
            setResponse('target component change')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target' })
          field.setValue(field.value ? field.value + 1 : 1)
        }}
      >
        Settings
      </button>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target' })
          field.setComponent('Input')
        }}
      >
        Set up components
      </button>
    </ActionResponse>
  )
}
```

## onFieldValueChange

Side effect hooks used to monitor changes in a field value

#### Signature

```ts
interface onFieldValueChange {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldValueChange } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldValueChange('target', (field) => {
            setResponse('target value change:' + field.value)
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target' })
          field.setValue(field.value ? field.value + 1 : 1)
        }}
      >
        Settings
      </button>
    </ActionResponse>
  )
}
```

## onFieldInitialValueChange

Side-effect hooks used to monitor changes in the default value of a field

#### Signature

```ts
interface onFieldInitialValueChange {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldInitialValueChange } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldInitialValueChange('target', (field) => {
            setResponse('target default value change:' + field.value)
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target' })
          field.setInitialValue(field.value ? field.value + 1 : 1)
        }}
      >
        Settings
      </button>
    </ActionResponse>
  )
}
```

## onFieldInputValueChange

Used to monitor the side effect hook triggered by a field onInput

#### Signature

```ts
interface onFieldInputValueChange {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldInputValueChange } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldInputValueChange('target', (field) => {
            setResponse('target value change:' + field.value)
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target' })
          field.onInput(field.value ? field.value + 1 : 1)
        }}
      >
        Call onInput
      </button>
    </ActionResponse>
  )
}
```

## onFieldValidateStart

#### Description

Monitor the side effect hook that triggers the start of a certain field verification

#### Signature

```ts
interface onFieldValidateStart {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldValidateStart } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldValidateStart('target', () => {
            setResponse('target verification start')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target', required: true })
          field.onInput('')
        }}
      >
        Trigger verification
      </button>
    </ActionResponse>
  )
}
```

## onFieldValidateEnd

#### Description

Monitor the side effect hook that triggers the end of a certain field verification

#### Signature

```ts
interface onFieldValidateEnd {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldValidateEnd } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldValidateEnd('target', () => {
            setResponse('target verification is over')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target', required: true })
          field.onInput('')
        }}
      >
        Trigger verification
      </button>
    </ActionResponse>
  )
}
```

## onFieldValidateFailed

#### Description

Listen to the side-effect hook of a field verification trigger failure

#### Signature

```ts
interface onFieldValidateFailed {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import { createForm, onFieldValidateFailed } from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldValidateFailed('target', () => {
            setResponse('target verification failed')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target', required: true })
          field.onInput('')
        }}
      >
        Trigger verification
      </button>
    </ActionResponse>
  )
}
```

## onFieldValidateSuccess

#### Description

Monitor the side effect hook that triggers a successful verification of a certain field

#### Signature

```ts
interface onFieldValidateSuccess {
  (pattern: FormPathPattern, callback: (field: Field, form: Form) => void)
}
```

#### Example

```tsx
import React, { useMemo, useState } from 'react'
import {
  createForm,
  onFieldValidateFailed,
  onFieldValidateSuccess,
} from '@formily/core'
import { ActionResponse } from './ActionResponse'

export default () => {
  const [response, setResponse] = useState('')
  const form = useMemo(
    () =>
      createForm({
        effects() {
          onFieldValidateFailed('target', () => {
            setResponse('target verification failed')
          })
          onFieldValidateSuccess('target', () => {
            setResponse('target verification succeeded')
          })
        },
      }),
    []
  )
  return (
    <ActionResponse response={response}>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target', required: true })
          field.onInput('')
        }}
      >
        Trigger failed
      </button>
      <button
        onClick={() => {
          const field = form.createField({ name: 'target', required: true })
          field.onInput('123')
        }}
      >
        Triggered successfully
      </button>
    </ActionResponse>
  )
}
```

```

--------------------------------------------------------------------------------
/packages/core/docs/guide/field.md:
--------------------------------------------------------------------------------

```markdown
# Field model

Formily's field model core contains two types of field models:

- Data type field
- Dummy data type field

Data type field (Field), the core is responsible for maintaining the form data (the value when the form is submitted).

VoidField, you can understand that it is a Field that has castrated data maintenance capabilities, so it is more of a UI form that maintains a batch of fields as a container.

Let's analyze these two types of fields in detail.

## Data field

There are 3 data type fields in the field model:

- Field
- ArrayField
- ObjectField

ArrayField and ObjectField are both inherited from Field. The positioning of Field is to maintain non-incremental data fields. Compared with ArrayField/Object, it does not mean that Field cannot store data of array type or object type. Field can store data of any data type. However, if the user expects to realize the interaction of adding, deleting, and moving arrays, they need to use ArrayField, and for the interaction of adding and deleting object properties, they need to use ObjectField. If there is no such requirement, all data types can be unified with Field.

Then let's look at the specific Field rules:

- Path rules
- Explicit and implicit rules
- Data read and write rules
- Data source rules
- Field component rules
- Field decorator rules
- Validation rules

### Path Rules

Because the form structure of our actual business itself is a tree structure, in Formily, each field will have an absolute path in the form model. This absolute path roughly describes the position of the field in the form data (why use roughly, later I will talk about it), any field can be found through the absolute path, and at the same time the parent-child relationship between the fields can be expressed. Therefore, in the field model, we define the address attribute to express the absolute path of the field, which is mainly described by dot syntax, such as abc The path of represents that the father of field c is field b, and the father of field b is a.

Of course, things are not that simple, because we also have a VoidField, which is a dummy data field, and it also has its own absolute path, because it can be the father of the data field. If we only have an absolute path, we cannot make a data field correct. Write field data to the form data. Reading data will also read the wrong position.

Therefore, we actually need a data path as a dedicated data field for writing data and reading data. Here we use path to describe the data path of the field. You can look at this picture for general rules:

![](//img.alicdn.com/imgextra/i1/O1CN01cdzULJ1et4PBak8si_!!6000000003928-2-tps-3506-2042.png)

In summary, Address is always the absolute path representing the node, and Path is the node path that skips the VoidField, but if it is the Path of the VoidField, it will retain its own path position.

Therefore, whether it is a Field or a VoidField, it will have its Address and Path, so when we use the query method to query the field, we can either use the Address rule to query, or use the Path rule to query, such as `query("bc")` The c field can be queried, and the c field can also be queried with `query("abc")`.

### Explicit and Implicit Rules

The display and hiding of the fields are mainly expressed by the display attribute:

- If display is none, it means that the field UI is hidden and the field data is not retained
- display is hidden, which means that the field UI is hidden and the field data is preserved
- display is visible, which means the field UI is displayed, and the field data is restored at the same time

On top of the display property, we also provide two convenient properties

1. visible, if true, display is equal to visible, if false, display is equal to none
2. hidden, if true, display is equal to hidden, if false, display is equal to visible

The above is about the writing rules of explicit and implicit attributes. The reading rules will be more complicated. Here is a default inheritance logic:

If the parent node actively sets the display property, and the child node does not actively set the display property, then the child node will inherit the display of the parent node

So what is the active setting of display? mainly includes:

- Configure the initial attributes display/visible/hidden for the field
- If there is no configuration during initialization, but display/visible/hidden is set to the field later

So what if you want to change from no inheritance to inheritance? Just set display to null.

### Data read and write rules

Because Field is a data-type field, it is responsible for maintaining the data of a certain node of the form data. The reading here is actually the form data read directly, which is addressed through the path attribute, which also guarantees the form data and field data. Absolutely idempotent, just read value/initialValue directly.

The data writing rules are consistent with the reading rules. Field does not independently maintain a copy of data. It directly operates on the data of the specific form, which is addressed through the path attribute. The main writing methods are:

- Modify the value/initialValue attribute directly
- Calling onInput will write data, and at the same time, set the inputValue of the field as input parameter data, inputValues as multi-parameter data, and then set the modified attribute to true, which means that the field has been manually modified, and finally trigger the verification rule that triggerType is onInput
- Call the setValue method

### Data source rules

Considering that the value source of the field is not only input through the Input input box, but also selected from a data source, such as a drop-down box, the field model adds a data source attribute dataSource, which is dedicated to reading data source. Only a layer of mapping needs to be done on the component consumer side. The method of writing to the data source can directly modify the dataSource property, or call the setDataSource method

### Component Rules

Field model, if there is no proxy UI component information, then more refined linkage control cannot be achieved. For example, if the value of A field changes to control the placeholder of B field, then the field attributes must be proxyed, so formily provides The component property is used to proxy UI component information. The component is an array `[Component,ComponentProps]`. The first element represents which component it is, and the second represents the properties of the component. Why use an array? This is convenient for type prompting, and the writing method is relatively simple.

The way to read component information is to read the component property directly.

The main ways to write component information are:

- Modify the component property directly and pass in the array
- Call the setComponent method, the first parameter is the component, the second is the component property
- Call the setComponentProps method to directly set the component properties

### Decorator rules

Similar to the field component rules, the field decorator is mainly used to maintain the package container of the field, such as FormItem, which is more partial to the control of the UI layout. Here we use the decorator attribute to describe the field decorator.

The way to read the decorator information is to directly read the decorator attribute.

The main ways to write decorator information are:

- Modify the decorator property directly and pass in an array
- Call the setDecorator method, the first parameter is the component, and the second is the component property
- Call the setDecoratorProps method to directly set the component properties

### Validation rules

The verification rules mainly include:

- Verifier
- Timing of calibration
- Verification strategy
- Verification result

#### Validator

The validator in the field model is mainly described by the validator attribute. The validator can be passed to the field when the field is initialized, and the validator can be modified again after initialization.

A validator mainly has the following forms:

- Pure string format verification, such as `"phone" | validator = "url" | validator= "email"`. This format verification is a short form of regular rules. Formily provides some standard regular rules. Of course Users can also manually create rules through registerValidateFormats to facilitate reuse
- Custom function verification, there are 3 return value modes:
  - `(value)=>"message"`, a string returned means there is an error, and no string means no error
  - `(value)=>({type:"error",message:"message"})`, return object form, you can specify type as error or warning or success
  - `{validator:()=>false,message:"message"}`, returns a boolean form, the error message will reuse the message field of the object structure
- Object structure verification is a more complete expression, such as:
  - `{format:"url"}` This can specify the regular format
  - `{required:true}` This can specify required fields
  - There are more rule attributes can refer to the API documentation, and we can also register similar validation rules through registerValidateRules
- Object array structure verification is a combination of the previous three types. In fact, the first three types will all be converted into object array structures, such as:
  - `["url",{required:true},(value)=>"message"]` is actually equivalent to `[{format:"url"},{required:true},{validator:(value)=> "message"}]`

#### Check timing

Sometimes, we want certain verification rules to be triggered only when focusing or out of focus. We can add a triggerType to each verification rule object, such as `{validator:(value)=>"message",triggerType: "onBlur"}` In this way, you can precisely control a verification rule to perform verification only in a certain event. The triggerType here mainly includes `"onInput" | "onBlur" | "onFocus"`, if you call `form. validate` is a rule that verifies all triggerTypes at once. If you manually call `field.validate`, you can specify the triggerType in the input parameters, and all triggerTypes will be verified if you don’t pass them.

#### Verification Strategy

Sometimes, we hope that the verification strategy of a certain field is that when all the verification rules are executed, if a verification rule fails, the result will be returned immediately. We only need to pass the parameter validateFirst to true when the field is initialized. That is, the default is false, that is, the verification will continue if the verification fails, and the verification result obtained is an array.

#### Read the verification result

The verification results are mainly stored in the feedbacks property in the field model. Feedbacks is an array of Feedback objects. The structure of each Feedback is:

```ts
interface Feedback {
  path: string //Field data path
  address: string //field absolute path
  type: 'error' | 'success' | 'warning' //Verification result type
  code: //Check result code
  | 'ValidateError'
    | 'ValidateSuccess'
    | 'ValidateWarning'
    | 'EffectError'
    | 'EffectSuccess'
    | 'EffectWarning'
  messages: string[] //Check the message
}
```

There are four main ways to read:

- Read feedbacks properties directly
- Reading the errors attribute is equivalent to filtering out all verification results with type error from feedbacks
- Reading the warnings attribute is equivalent to filtering out all the verification results whose type is warning from feedbacks
- Reading the successes attribute is equivalent to filtering out all verification results with type success from feedbacks

#### Write verification result

There are 3 ways to write:

- Call the validate method to trigger the field validator to perform the validation action, and the code of the validation result is uniformly Validate\*`
  - Calling onInput will trigger validate
  - Calling onFocus will trigger validate
  - Calling onBlur will trigger validate
  - Call reset and specify validate as true to trigger validate
- Modify the feedbacks attribute directly
- Modify the errors property directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectError
- Modify the warnings attribute directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectWarning
- Modify the successes property directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectSuccess

Such writing logic is mainly to prevent users from modifying the verification results from polluting the verification results of their own verifiers, strictly separating them, and easy to restore the scene.

#### Verification result query

The query of the verification result is mainly queried through queryFeedbacks. The query has the same participating Feedback objects, which can be filtered by type or code, or by path.

## ArrayField

Compared with Field, ArrayField only extends array-related methods on the basis of inheriting Field, such as push/pop/insert/move. Why should these methods be provided? Its ability is not only to process the field data, it It also provides internal state transposition processing for the sub-nodes of ArrayField mainly to ensure that the order of the fields is consistent with the order of the data. Can cite an example:

![](//img.alicdn.com/imgextra/i3/O1CN01mpGugu1QFlnfQ4qfo_!!6000000001947-2-tps-3506-1794.png)

This is a move call process, the value of the array element will move, and the state of the corresponding field will also move.

## ObjectField

Because the object type is disordered, there is no state transposition, so ObjectField provides addProperty/removeProperty/existProperty three APIs for users to use.

## VoidField

Compared with Field, VoidField mainly castrates data read and write rules, data source rules, and verification rules. When users use it, they mainly use explicit and implicit rules, components, and decorator rules.

<Alert>

The series of field rules mentioned above did not mention the detailed API usage details. It is more to help users sort out formily from the perspective of ideas. If you are not familiar with the API, it is best to read the API documentation first.

</Alert>

```

--------------------------------------------------------------------------------
/packages/antd/docs/components/FormGrid.zh-CN.md:
--------------------------------------------------------------------------------

```markdown
# FormGrid

> FormGrid 组件

## Markup Schema 案例

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/antd'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Void
          x-component="FormGrid"
          x-component-props={{
            maxColumns: 3,
            minColumns: 2,
          }}
        >
          <SchemaField.String
            name="aaa"
            title="aaa"
            x-decorator="FormItem"
            x-decorator-props={{ gridSpan: 2 }}
            x-component="Input"
          />
          <SchemaField.String
            name="bbb"
            title="bbb"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ccc"
            title="ccc"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ddd"
            title="ddd"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="eee"
            title="eee"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="fff"
            title="fff"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ggg"
            title="ggg"
            x-decorator="FormItem"
            x-component="Input"
          />
        </SchemaField.Void>
      </SchemaField>
    </FormProvider>
  )
}
```

## JSON Schema 案例

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/antd'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

const schema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      'x-component-props': {
        minColumns: [4, 6, 10],
      },
      properties: {
        aaa: {
          type: 'string',
          title: 'AAA',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        bbb: {
          type: 'string',
          title: 'BBB',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ccc: {
          type: 'string',
          title: 'CCC',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ddd: {
          type: 'string',
          title: 'DDD',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        eee: {
          type: 'string',
          title: 'EEE',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        fff: {
          type: 'string',
          title: 'FFF',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ggg: {
          type: 'string',
          title: 'GGG',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
      },
    },
  },
}

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField schema={schema} />
    </FormProvider>
  )
}
```

## 原生 案例

```tsx
import React from 'react'
import { FormGrid } from '@formily/antd'

const { GridColumn } = FormGrid
const Cell = ({ children }) => {
  return (
    <div
      style={{
        backgroundColor: '#AAA',
        color: '#FFF',
        height: 30,
        display: 'flex',
        alignItems: 'center',
        padding: '0 10px',
      }}
    >
      {children}
    </div>
  )
}
export default () => {
  return (
    <React.Fragment>
      <p>maxColumns 3 + minColumns 2</p>
      <FormGrid maxColumns={3} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={4}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxColumns 3</p>
      <FormGrid maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minColumns 2</p>
      <FormGrid minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>Null</p>
      <FormGrid columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minWidth 150 +maxColumns 3</p>
      <FormGrid minWidth={150} maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120+minColumns 2</p>
      <FormGrid maxWidth={120} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120 + gridSpan -1</p>
      <FormGrid maxWidth={120} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn gridSpan={-1}>
          <Cell>3</Cell>
        </GridColumn>
      </FormGrid>
    </React.Fragment>
  )
}
```

## 查询表单实现案例

```tsx
import React, { useMemo, Fragment } from 'react'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, observer } from '@formily/react'
import {
  Form,
  Input,
  Select,
  DatePicker,
  FormItem,
  FormGrid,
  Submit,
  Reset,
  FormButtonGroup,
} from '@formily/antd'

const useCollapseGrid = (maxRows: number) => {
  const grid = useMemo(
    () =>
      FormGrid.createFormGrid({
        maxColumns: 4,
        maxWidth: 240,
        maxRows: maxRows,
        shouldVisible: (node, grid) => {
          if (node.index === grid.childSize - 1) return true
          if (grid.maxRows === Infinity) return true
          return node.shadowRow < maxRows + 1
        },
      }),
    []
  )
  const expanded = grid.maxRows === Infinity
  const realRows = grid.shadowRows
  const computeRows = grid.fullnessLastColumn
    ? grid.shadowRows - 1
    : grid.shadowRows

  const toggle = () => {
    if (grid.maxRows === Infinity) {
      grid.maxRows = maxRows
    } else {
      grid.maxRows = Infinity
    }
  }
  const takeType = () => {
    if (realRows < maxRows + 1) return 'incomplete-wrap'
    if (computeRows > maxRows) return 'collapsible'
    return 'complete-wrap'
  }
  return {
    grid,
    expanded,
    toggle,
    type: takeType(),
  }
}

const QueryForm: React.FC = observer((props) => {
  const { grid, expanded, toggle, type } = useCollapseGrid(1)

  const renderActions = () => {
    return (
      <Fragment>
        <Submit onSubmit={console.log}>查询</Submit>
        <Reset>重置</Reset>
      </Fragment>
    )
  }

  const renderButtonGroup = () => {
    if (type === 'incomplete-wrap') {
      return (
        <FormButtonGroup.FormItem>
          <FormButtonGroup>{renderActions()}</FormButtonGroup>
        </FormButtonGroup.FormItem>
      )
    }
    if (type === 'collapsible') {
      return (
        <Fragment>
          <FormButtonGroup>
            <a
              href=""
              onClick={(e) => {
                e.preventDefault()
                toggle()
              }}
            >
              {expanded ? '收起' : '展开'}
            </a>
          </FormButtonGroup>
          <FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
        </Fragment>
      )
    }
    return (
      <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}>
        {renderActions()}
      </FormButtonGroup>
    )
  }

  return (
    <Form {...props} layout="vertical" feedbackLayout="terse">
      <FormGrid grid={grid}>
        {props.children}
        <FormGrid.GridColumn
          gridSpan={-1}
          style={{ display: 'flex', justifyContent: 'space-between' }}
        >
          {renderButtonGroup()}
        </FormGrid.GridColumn>
      </FormGrid>
    </Form>
  )
})

const SchemaField = createSchemaField({
  components: {
    QueryForm,
    Input,
    Select,
    DatePicker,
    FormItem,
  },
})

export default () => {
  const form = useMemo(() => createForm(), [])
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Object x-component="QueryForm">
          <SchemaField.String
            name="input1"
            title="Input 1"
            x-component="Input"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="input2"
            title="Input 2"
            x-component="Input"
            x-decorator="FormItem"
          />

          <SchemaField.String
            name="select1"
            title="Select 1"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="select2"
            title="Select 2"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="date"
            title="DatePicker"
            x-component="DatePicker"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="dateRange"
            title="DatePicker.RangePicker"
            x-component="DatePicker.RangePicker"
            x-decorator="FormItem"
            x-decorator-props={{
              gridSpan: 2,
            }}
          />
          <SchemaField.String
            name="select3"
            title="Select 3"
            x-component="Select"
            x-decorator="FormItem"
          />
        </SchemaField.Object>
      </SchemaField>
    </FormProvider>
  )
}
```

## API

### FormGrid

| 属性名        | 类型                   | 描述                                                           | 默认值            |
| ------------- | ---------------------- | -------------------------------------------------------------- | ----------------- |
| minWidth      | `number \| number[]`   | 元素最小宽度                                                   | 100               |
| maxWidth      | `number \| number[]`   | 元素最大宽度                                                   | -                 |
| minColumns    | `number \| number[]`   | 最小列数                                                       | 0                 |
| maxColumns    | `number \| number[]`   | 最大列数                                                       | -                 |
| breakpoints   | number[]               | 容器尺寸断点                                                   | `[720,1280,1920]` |
| columnGap     | number                 | 列间距                                                         | 8                 |
| rowGap        | number                 | 行间距                                                         | 4                 |
| colWrap       | boolean                | 自动换行                                                       | true              |
| strictAutoFit | boolean                | GridItem 宽度是否严格受限于 maxWidth,不受限的话会自动占满容器 | false             |
| shouldVisible | `(node,grid)=>boolean` | 是否需要显示当前节点                                           | `()=>true`        |
| grid          | `Grid`                 | 外部传入 Grid 实例,用于实现更复杂的布局逻辑                   | -                 |

注意:

- minWidth 生效优先级高于 minColumn
- maxWidth 优先级高于 maxColumn
- minWidth/maxWidth/minColumns/maxColumns 的数组格式代表与断点数组映射

### FormGrid.GridColumn

| 属性名   | 类型   | 描述                                                 | 默认值 |
| -------- | ------ | ---------------------------------------------------- | ------ |
| gridSpan | number | 元素所跨列数,如果为-1,那么会自动反向跨列填补单元格 | 1      |

### FormGrid.createFormGrid

从上下文中读取 Grid 实例

```ts
interface createFormGrid {
  (props: IGridProps): Grid
}
```

- IGridProps 参考 FormGrid 属性
- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid

### FormGrid.useFormGrid

从上下文中读取 Grid 实例

```ts
interface useFormGrid {
  (): Grid
}
```

- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid

```

--------------------------------------------------------------------------------
/packages/next/docs/components/FormGrid.zh-CN.md:
--------------------------------------------------------------------------------

```markdown
# FormGrid

> FormGrid 组件

## Markup Schema 案例

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/next'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Void
          x-component="FormGrid"
          x-component-props={{
            maxColumns: 3,
            minColumns: 2,
          }}
        >
          <SchemaField.String
            name="aaa"
            title="aaa"
            x-decorator="FormItem"
            x-decorator-props={{ gridSpan: 2 }}
            x-component="Input"
          />
          <SchemaField.String
            name="bbb"
            title="bbb"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ccc"
            title="ccc"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ddd"
            title="ddd"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="eee"
            title="eee"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="fff"
            title="fff"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ggg"
            title="ggg"
            x-decorator="FormItem"
            x-component="Input"
          />
        </SchemaField.Void>
      </SchemaField>
    </FormProvider>
  )
}
```

## JSON Schema 案例

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/next'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

const schema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      'x-component-props': {
        minColumns: [4, 6, 10],
      },
      properties: {
        aaa: {
          type: 'string',
          title: 'AAA',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        bbb: {
          type: 'string',
          title: 'BBB',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ccc: {
          type: 'string',
          title: 'CCC',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ddd: {
          type: 'string',
          title: 'DDD',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        eee: {
          type: 'string',
          title: 'EEE',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        fff: {
          type: 'string',
          title: 'FFF',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ggg: {
          type: 'string',
          title: 'GGG',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
      },
    },
  },
}

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField schema={schema} />
    </FormProvider>
  )
}
```

## 原生 案例

```tsx
import React from 'react'
import { FormGrid } from '@formily/next'

const { GridColumn } = FormGrid
const Cell = ({ children }) => {
  return (
    <div
      style={{
        backgroundColor: '#AAA',
        color: '#FFF',
        height: 30,
        display: 'flex',
        alignItems: 'center',
        padding: '0 10px',
      }}
    >
      {children}
    </div>
  )
}
export default () => {
  return (
    <React.Fragment>
      <p>maxColumns 3 + minColumns 2</p>
      <FormGrid maxColumns={3} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={4}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxColumns 3</p>
      <FormGrid maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minColumns 2</p>
      <FormGrid minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>Null</p>
      <FormGrid columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minWidth 150 +maxColumns 3</p>
      <FormGrid minWidth={150} maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120+minColumns 2</p>
      <FormGrid maxWidth={120} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120 + gridSpan -1</p>
      <FormGrid maxWidth={120} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn gridSpan={-1}>
          <Cell>3</Cell>
        </GridColumn>
      </FormGrid>
    </React.Fragment>
  )
}
```

## 查询表单实现案例

```tsx
import React, { useMemo, Fragment } from 'react'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, observer } from '@formily/react'
import {
  Form,
  Input,
  Select,
  DatePicker,
  FormItem,
  FormGrid,
  Submit,
  Reset,
  FormButtonGroup,
} from '@formily/next'

const useCollapseGrid = (maxRows: number) => {
  const grid = useMemo(
    () =>
      FormGrid.createFormGrid({
        maxColumns: 4,
        maxWidth: 240,
        maxRows: maxRows,
        shouldVisible: (node, grid) => {
          if (node.index === grid.childSize - 1) return true
          if (grid.maxRows === Infinity) return true
          return node.shadowRow < maxRows + 1
        },
      }),
    []
  )
  const expanded = grid.maxRows === Infinity
  const realRows = grid.shadowRows
  const computeRows = grid.fullnessLastColumn
    ? grid.shadowRows - 1
    : grid.shadowRows

  const toggle = () => {
    if (grid.maxRows === Infinity) {
      grid.maxRows = maxRows
    } else {
      grid.maxRows = Infinity
    }
  }
  const takeType = () => {
    if (realRows < maxRows + 1) return 'incomplete-wrap'
    if (computeRows > maxRows) return 'collapsible'
    return 'complete-wrap'
  }
  return {
    grid,
    expanded,
    toggle,
    type: takeType(),
  }
}

const QueryForm: React.FC = observer((props) => {
  const { grid, expanded, toggle, type } = useCollapseGrid(1)

  const renderActions = () => {
    return (
      <Fragment>
        <Submit onSubmit={console.log}>查询</Submit>
        <Reset>重置</Reset>
      </Fragment>
    )
  }

  const renderButtonGroup = () => {
    if (type === 'incomplete-wrap') {
      return (
        <FormButtonGroup.FormItem>
          <FormButtonGroup>{renderActions()}</FormButtonGroup>
        </FormButtonGroup.FormItem>
      )
    }
    if (type === 'collapsible') {
      return (
        <Fragment>
          <FormButtonGroup>
            <a
              href=""
              onClick={(e) => {
                e.preventDefault()
                toggle()
              }}
            >
              {expanded ? '收起' : '展开'}
            </a>
          </FormButtonGroup>
          <FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
        </Fragment>
      )
    }
    return (
      <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}>
        {renderActions()}
      </FormButtonGroup>
    )
  }

  return (
    <Form {...props} layout="vertical" feedbackLayout="terse">
      <FormGrid grid={grid}>
        {props.children}
        <FormGrid.GridColumn
          gridSpan={-1}
          style={{ display: 'flex', justifyContent: 'space-between' }}
        >
          {renderButtonGroup()}
        </FormGrid.GridColumn>
      </FormGrid>
    </Form>
  )
})

const SchemaField = createSchemaField({
  components: {
    QueryForm,
    Input,
    Select,
    DatePicker,
    FormItem,
  },
})

export default () => {
  const form = useMemo(() => createForm(), [])
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Object x-component="QueryForm">
          <SchemaField.String
            name="input1"
            title="Input 1"
            x-component="Input"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="input2"
            title="Input 2"
            x-component="Input"
            x-decorator="FormItem"
          />

          <SchemaField.String
            name="select1"
            title="Select 1"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="select2"
            title="Select 2"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="date"
            title="DatePicker"
            x-component="DatePicker"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="dateRange"
            title="DatePicker.RangePicker"
            x-component="DatePicker.RangePicker"
            x-decorator="FormItem"
            x-decorator-props={{
              gridSpan: 2,
            }}
          />
          <SchemaField.String
            name="select3"
            title="Select 3"
            x-component="Select"
            x-decorator="FormItem"
          />
        </SchemaField.Object>
      </SchemaField>
    </FormProvider>
  )
}
```

## API

### FormGrid

| 属性名        | 类型                   | 描述                                                           | 默认值            |
| ------------- | ---------------------- | -------------------------------------------------------------- | ----------------- |
| minWidth      | `number \| number[]`   | 元素最小宽度                                                   | 100               |
| maxWidth      | `number \| number[]`   | 元素最大宽度                                                   | -                 |
| minColumns    | `number \| number[]`   | 最小列数                                                       | 0                 |
| maxColumns    | `number \| number[]`   | 最大列数                                                       | -                 |
| breakpoints   | number[]               | 容器尺寸断点                                                   | `[720,1280,1920]` |
| columnGap     | number                 | 列间距                                                         | 8                 |
| rowGap        | number                 | 行间距                                                         | 4                 |
| colWrap       | boolean                | 自动换行                                                       | true              |
| strictAutoFit | boolean                | GridItem 宽度是否严格受限于 maxWidth,不受限的话会自动占满容器 | false             |
| shouldVisible | `(node,grid)=>boolean` | 是否需要显示当前节点                                           | `()=>true`        |
| grid          | `Grid`                 | 外部传入 Grid 实例,用于实现更复杂的布局逻辑                   | -                 |

注意:

- minWidth 生效优先级高于 minColumn
- maxWidth 优先级高于 maxColumn
- minWidth/maxWidth/minColumns/maxColumns 的数组格式代表与断点数组映射

### FormGrid.GridColumn

| 属性名   | 类型   | 描述                                                 | 默认值 |
| -------- | ------ | ---------------------------------------------------- | ------ |
| gridSpan | number | 元素所跨列数,如果为-1,那么会自动反向跨列填补单元格 | 1      |

### FormGrid.createFormGrid

从上下文中读取 Grid 实例

```ts
interface createFormGrid {
  (props: IGridProps): Grid
}
```

- IGridProps 参考 FormGrid 属性
- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid

### FormGrid.useFormGrid

从上下文中读取 Grid 实例

```ts
interface useFormGrid {
  (): Grid
}
```

- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid

```

--------------------------------------------------------------------------------
/packages/vue/src/__tests__/field.spec.ts:
--------------------------------------------------------------------------------

```typescript
import Vue, { FunctionalComponentOptions } from 'vue'
import { render, fireEvent, waitFor } from '@testing-library/vue'
import { defineComponent, h, ref } from '@vue/composition-api'
import {
  createForm,
  Field as FieldType,
  isField,
  isVoidField,
  onFieldChange,
} from '@formily/core'
import { useField, useFormEffects, connect, mapProps, mapReadPretty } from '../'
import {
  FormProvider,
  ArrayField,
  ObjectField,
  VoidField,
  Field,
} from '../vue2-components'
import ReactiveField from '../components/ReactiveField'
// import { expectThrowError } from './shared'

Vue.component('FormProvider', FormProvider)
Vue.component('ArrayField', ArrayField)
Vue.component('ObjectField', ObjectField)
Vue.component('VoidField', VoidField)
Vue.component('Field', Field)
Vue.component('ReactiveField', ReactiveField as unknown as Vue)

const Decorator = defineComponent({
  props: ['label'],
  render(h) {
    return h(
      'div',
      {
        attrs: this.$attrs,
      },
      [this.label, this.$slots.default]
    )
  },
})

const Input = defineComponent({
  props: ['value'],
  setup(props, { attrs, listeners }) {
    const fieldRef = useField()
    return () => {
      const field = fieldRef.value
      return h('input', {
        class: 'test-input',
        attrs: {
          ...attrs,
          value: props.value,
          'data-testid': field.path.toString(),
        },
        on: {
          ...listeners,
          input: listeners.change,
        },
      })
    }
  },
})

const Normal: FunctionalComponentOptions = {
  functional: true,
  render(h) {
    return h('div')
  },
}

test('render field', async () => {
  const form = createForm()
  const onChange = jest.fn()
  const atChange = jest.fn()
  const atBlur = jest.fn()
  const atFocus = jest.fn()

  const { getByTestId, queryByTestId, queryByText } = render(
    defineComponent({
      name: 'TestComponent',
      setup() {
        return {
          form,
          Normal,
          Input,
          Decorator,
          onChange,
          atChange,
          atFocus,
          atBlur,
        }
      },
      template: `<FormProvider :form="form">
      <Field
        name="aa"
        :decorator="[Decorator, {label: 'aa-decorator'}]"
        :component="[Input, { onChange }]"
      />
      <ArrayField name="bb" :decorator="[Decorator]">
        <div data-testid="bb-children"></div>
      </ArrayField>
      <ObjectField name="cc" :decorator="[Decorator]">
        <Field name="mm" :decorator="[Decorator]" :component="[Input]" />
        <ObjectField name="pp" :decorator="[Decorator]" />
        <ArrayField name="tt" :decorator="[Decorator]" />
        <VoidField name="ww" />
      </ObjectField>
      <VoidField name="dd" :decorator="[Decorator]">
        <div data-testid="dd-children">
          <Field name="oo" :decorator="[Decorator]" :component="[Input]" />
        </div>
      </VoidField>
      <VoidField name="xx" :decorator="[Decorator]" :component="[Normal]" />
      <Field
        name="ee"
        :visible="false"
        :decorator="[Decorator]"
        :component="[Input]"
      />
      <Field name="ff" :decorator="[]" :component="[]" />
      <Field name="gg" :decorator="null" :component="null" />
      <Field name="hh" :decorator="[null]" :component="[null, null]" />
      <Field
        name="kk"
        :decorator="[Decorator]"
        :component="[Input, { onChange: null }]"
      />
      <Field
        name="ll"
        :decorator="[Decorator]"
        :component="[Input, { '@change': atChange, '@focus': atFocus, '@blur': atBlur }]"
      />
      <Field
        name="mm"
        :decorator="[Decorator]"
      ><div data-testid="mm-children"></div></Field>
    </FormProvider>`,
    })
  )
  expect(form.mounted).toBeTruthy()
  expect(form.query('aa').take().mounted).toBeTruthy()
  expect(form.query('bb').take().mounted).toBeTruthy()
  expect(form.query('cc').take().mounted).toBeTruthy()
  expect(form.query('dd').take().mounted).toBeTruthy()
  await fireEvent.update(getByTestId('aa'), '123')
  await fireEvent.update(getByTestId('kk'), '123')
  await fireEvent.focus(getByTestId('ll'))
  await fireEvent.blur(getByTestId('ll'))
  await fireEvent.update(getByTestId('ll'), '123')
  expect(onChange).toBeCalledTimes(1)
  expect(atChange).toBeCalledTimes(1)
  expect(atFocus).toBeCalledTimes(1)
  expect(atBlur).toBeCalledTimes(1)
  expect(getByTestId('bb-children')).not.toBeUndefined()
  expect(getByTestId('dd-children')).not.toBeUndefined()
  expect(queryByTestId('ee')).toBeNull()
  expect(form.query('aa').get('value')).toEqual('123')
  expect(form.query('kk').get('value')).toEqual('123')
  expect(getByTestId('mm-children')).not.toBeUndefined()
  expect(queryByText('aa-decorator')).not.toBeNull()
})

const InputWithSlot = defineComponent({
  props: ['value'],
  setup(props, { attrs, listeners, slots }) {
    const fieldRef = useField()
    return () => {
      const field = fieldRef.value
      return h('div', {}, [
        h('input', {
          class: 'test-input',
          attrs: {
            ...attrs,
            value: props.value,
            'data-testid': field.path.toString(),
          },
          on: {
            ...listeners,
            input: listeners.change,
          },
        }),
        [slots['append']?.({ path: field.path.toString() })],
      ])
    }
  },
})

test('render in nesting slots with (ObjectField/ArrayField) no decorator', async () => {
  const form = createForm()

  const { getByTestId } = render(
    defineComponent({
      name: 'TestComponent',
      setup() {
        return {
          form,
          Normal,
          InputWithSlot,
          Decorator,
        }
      },
      template: `<FormProvider :form="form">
      <ObjectField name="cc" :component="['div']">
        <Field name="mm" :decorator="[Decorator]" :component="[InputWithSlot]">
          <template #append="{ path }">
            <span :data-testid="'slot-prop-' +path"></span>
          </template>
        </Field>
      </ObjectField>
      <VoidField name="dd" :component="['div']">
        <Field name="oo" :decorator="[Decorator]" :component="[InputWithSlot]" />
      </VoidField>
      
    </FormProvider>`,
    })
  )

  expect(getByTestId('oo')).not.toBeUndefined()
  expect(getByTestId('cc.mm')).not.toBeUndefined()
  expect(getByTestId('slot-prop-cc.mm')).not.toBeUndefined()
})

test('render field with html attrs', async () => {
  const form = createForm()

  const { getByTestId, container } = render(
    defineComponent({
      name: 'TestComponent',
      setup() {
        return {
          form,
          Input,
          Decorator,
        }
      },
      template: `<FormProvider :form="form">
      <Field
        name="aa"
        :decorator="[Decorator, {
          'data-testid': 'decorator',
          class: {
            'test-class': true
          },
          style: {
            marginRight: '10px'
          }
        }]"
        :component="[Input, {
          class: {
            'test-class': true
          },
          style: {
            marginLeft: '10px'
          }
        }]"
      />
    </FormProvider>`,
    })
  )
  expect(form.mounted).toBeTruthy()
  expect(form.query('aa').take().mounted).toBeTruthy()
  expect(getByTestId('aa').className.indexOf('test-input') !== -1).toBeTruthy()
  expect(getByTestId('aa').className.indexOf('test-class') !== -1).toBeTruthy()
  expect(getByTestId('aa').style.marginLeft).toEqual('10px')
  expect(
    getByTestId('decorator').className.indexOf('test-class') !== -1
  ).toBeTruthy()
  expect(getByTestId('decorator').style.marginRight).toEqual('10px')
})

test('ReactiveField', () => {
  render({
    template: `<ReactiveField />`,
  })
  render({
    template: `<ReactiveField>
      <div></div>
    </ReactiveField>`,
  })
})

test('useAttch', async () => {
  const form1 = createForm()
  const MyComponent = defineComponent({
    props: ['form', 'name1', 'name2', 'name3', 'name4'],
    data() {
      return { Input, Decorator }
    },
    template: `<FormProvider :form="form">
      <Field :name="name1" :decorator="[Decorator]" :component="[Input]" />
      <ArrayField :name="name2" :decorator="[Decorator]" :component="[Input]" />
      <ObjectField :name="name3" :decorator="[Decorator]" :component="[Input]" />
      <VoidField :name="name4" :decorator="[Decorator]" :component="[Input]" />
    </FormProvider>`,
  })
  const { updateProps } = render(MyComponent, {
    props: {
      form: form1,
      name1: 'aa',
      name2: 'bb',
      name3: 'cc',
      name4: 'dd',
    },
  })
  expect(form1.mounted).toBeTruthy()
  expect(form1.query('aa').take().mounted).toBeTruthy()
  expect(form1.query('bb').take().mounted).toBeTruthy()
  expect(form1.query('cc').take().mounted).toBeTruthy()
  expect(form1.query('dd').take().mounted).toBeTruthy()
  await updateProps({
    name1: 'aaa',
    name2: 'bbb',
    name3: 'ccc',
    name4: 'ddd',
  })
  await Vue.nextTick()
  expect(form1.query('aa').take().mounted).toBeFalsy()
  expect(form1.query('bb').take().mounted).toBeFalsy()
  expect(form1.query('cc').take().mounted).toBeFalsy()
  expect(form1.query('dd').take().mounted).toBeFalsy()
  expect(form1.query('aaa').take().mounted).toBeTruthy()
  expect(form1.query('bbb').take().mounted).toBeTruthy()
  expect(form1.query('ccc').take().mounted).toBeTruthy()
  expect(form1.query('ddd').take().mounted).toBeTruthy()
  const form2 = createForm()
  await updateProps({
    form: form2,
  })
  await Vue.nextTick()
  expect(form1.unmounted).toBeTruthy()
  expect(form2.mounted).toBeTruthy()
})

test('useFormEffects', async () => {
  const form = createForm()
  const CustomField = defineComponent({
    props: ['value'],
    setup(props) {
      const fieldRef = useField<FieldType>()
      useFormEffects(() => {
        onFieldChange('aa', ['value'], (target) => {
          if (isVoidField(target)) return
          fieldRef.value.setValue(target.value)
        })
      })
      return () => {
        return h('div', { attrs: { 'data-testid': 'custom-value' } }, [
          props.value,
        ])
      }
    },
  })
  const { queryByTestId } = render({
    data() {
      return { form, Decorator, Input, CustomField }
    },
    template: `<FormProvider :form="form">
      <Field name="aa" :decorator="[Decorator]" :component="[Input]" />
      <Field name="bb" :component="[CustomField]" />
    </FormProvider>`,
  })
  expect(queryByTestId('custom-value').textContent).toEqual('')
  form.query('aa').take((aa) => {
    if (isField(aa)) {
      const value = '123' as any
      aa.setValue(value)
    }
  })
  await waitFor(() => {
    expect(queryByTestId('custom-value').textContent).toEqual('123')
  })
})

test('useFormEffects: should be reregister when formRef change', async () => {
  const CustomField = defineComponent({
    setup() {
      const reactiveText = ref()
      useFormEffects(() => {
        onFieldChange('aa', ['value'], (target) => {
          if (isVoidField(target)) return
          reactiveText.value = target.value
        })
      })
      return () =>
        h('div', { attrs: { 'data-testid': 'custom-value' } }, [
          reactiveText.value,
        ])
    },
  })

  const { queryByTestId } = render({
    setup() {
      const formRef = ref(createForm())
      return {
        formRef,
        Input,
        CustomField,
        changeForm() {
          // form change
          formRef.value = createForm()
          formRef.value.setValues({ aa: 'text' })
        },
      }
    },
    template: `<FormProvider :form="formRef">
      <Field name="aa" :decorator="[Decorator]" :component="[Input]" />
      <VoidField name="bb" :component="[CustomField]" />
      <button data-testid="btn" @click="changeForm()">Change</button>
    </FormProvider>`,
  })

  expect(queryByTestId('custom-value').textContent).toEqual('')
  queryByTestId('btn').click()
  await waitFor(() => {
    expect(queryByTestId('custom-value').textContent).toEqual('text')
  })
})

test('connect', async () => {
  const CustomField = connect(
    {
      functional: true,
      props: ['list'],
      render(h, context) {
        return h('div', [context.props.list])
      },
    },
    mapProps({ value: 'list', loading: true }, (props, field) => {
      return {
        ...props,
        mounted: field.mounted ? 1 : 2,
      }
    }),
    mapReadPretty({
      render(h) {
        return h('div', 'read pretty')
      },
    })
  )
  const BaseComponent = {
    functional: true,
    name: 'BaseComponent',
    render(h, context) {
      return h('div', [context.props.value])
    },
  } as FunctionalComponentOptions
  const CustomField2 = connect(
    BaseComponent,
    mapProps({ value: true, loading: true }),
    mapReadPretty({
      render(h) {
        return h('div', 'read pretty')
      },
    })
  )

  const CustomField3 = connect(
    Input,
    mapProps(),
    mapReadPretty({
      render(h) {
        return h('div', 'read pretty')
      },
    })
  )

  const CustomFormItem = connect(
    {
      functional: true,
      render(h, context) {
        return h('div', context.data, context.children)
      },
    },
    mapProps(),
    mapReadPretty({
      render(h) {
        return h('div', 'read pretty')
      },
    })
  )

  const form = createForm()
  const { queryByText, getByTestId } = render({
    data() {
      return {
        form,
        Decorator,
        CustomField,
        CustomField2,
        CustomField3,
        CustomFormItem,
      }
    },
    template: `<FormProvider :form="form">
      <Field name="aa" :decorator="[Decorator]" :component="[CustomField]" />
      <Field name="bb" :decorator="[Decorator]" :component="[CustomField2]" />
      <Field name="cc" :decorator="[Decorator]" :component="[CustomField3]" />
      <component :is="CustomFormItem">dd</component>
    </FormProvider>`,
  })
  form.query('aa').take((field) => {
    field.setState((state) => {
      state.value = '123'
    })
  })

  expect(queryByText('dd')).toBeVisible()
  await waitFor(() => {
    expect(queryByText('123')).toBeVisible()
  })

  fireEvent.update(getByTestId('cc'), '123')
  expect(queryByText('123')).toBeVisible()
  expect(form.query('cc').get('value')).toEqual('123')

  form.query('aa').take((field) => {
    if (!isField(field)) return
    field.readPretty = true
  })
  await waitFor(() => {
    expect(queryByText('123')).toBeNull()
    expect(queryByText('read pretty')).toBeVisible()
  })
})

```

--------------------------------------------------------------------------------
/packages/antd/docs/components/FormGrid.md:
--------------------------------------------------------------------------------

```markdown
# FormGrid

> FormGrid component

## Markup Schema example

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/antd'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Void
          x-component="FormGrid"
          x-component-props={{
            maxColumns: 3,
            minColumns: 2,
          }}
        >
          <SchemaField.String
            name="aaa"
            title="aaa"
            x-decorator="FormItem"
            x-decorator-props={{ gridSpan: 2 }}
            x-component="Input"
          />
          <SchemaField.String
            name="bbb"
            title="bbb"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ccc"
            title="ccc"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ddd"
            title="ddd"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="eee"
            title="eee"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="fff"
            title="fff"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ggg"
            title="ggg"
            x-decorator="FormItem"
            x-component="Input"
          />
        </SchemaField.Void>
      </SchemaField>
    </FormProvider>
  )
}
```

## JSON Schema case

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/antd'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

const schema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      'x-component-props': {
        minColumns: [4, 6, 10],
      },
      properties: {
        aaa: {
          type: 'string',
          title: 'AAA',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        bbb: {
          type: 'string',
          title: 'BBB',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ccc: {
          type: 'string',
          title: 'CCC',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ddd: {
          type: 'string',
          title: 'DDD',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        eee: {
          type: 'string',
          title: 'EEE',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        fff: {
          type: 'string',
          title: 'FFF',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ggg: {
          type: 'string',
          title: 'GGG',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
      },
    },
  },
}

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField schema={schema} />
    </FormProvider>
  )
}
```

## Native case

```tsx
import React from 'react'
import { FormGrid } from '@formily/antd'

const { GridColumn } = FormGrid
const Cell = ({ children }) => {
  return (
    <div
      style={{
        backgroundColor: '#AAA',
        color: '#FFF',
        height: 30,
        display: 'flex',
        alignItems: 'center',
        padding: '0 10px',
      }}
    >
      {children}
    </div>
  )
}
export default () => {
  return (
    <React.Fragment>
      <p>maxColumns 3 + minColumns 2</p>
      <FormGrid maxColumns={3} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={4}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxColumns 3</p>
      <FormGrid maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minColumns 2</p>
      <FormGrid minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>Null</p>
      <FormGrid columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minWidth 150 +maxColumns 3</p>
      <FormGrid minWidth={150} maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120+minColumns 2</p>
      <FormGrid maxWidth={120} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120 + gridSpan -1</p>
      <FormGrid maxWidth={120} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn gridSpan={-1}>
          <Cell>3</Cell>
        </GridColumn>
      </FormGrid>
    </React.Fragment>
  )
}
```

## Query Form case

```tsx
import React, { useMemo, Fragment } from 'react'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, observer } from '@formily/react'
import {
  Form,
  Input,
  Select,
  DatePicker,
  FormItem,
  FormGrid,
  Submit,
  Reset,
  FormButtonGroup,
} from '@formily/antd'

const useCollapseGrid = (maxRows: number) => {
  const grid = useMemo(
    () =>
      FormGrid.createFormGrid({
        maxColumns: 4,
        maxWidth: 240,
        maxRows: maxRows,
        shouldVisible: (node, grid) => {
          if (node.index === grid.childSize - 1) return true
          if (grid.maxRows === Infinity) return true
          return node.shadowRow < maxRows + 1
        },
      }),
    []
  )
  const expanded = grid.maxRows === Infinity
  const realRows = grid.shadowRows
  const computeRows = grid.fullnessLastColumn
    ? grid.shadowRows - 1
    : grid.shadowRows

  const toggle = () => {
    if (grid.maxRows === Infinity) {
      grid.maxRows = maxRows
    } else {
      grid.maxRows = Infinity
    }
  }
  const takeType = () => {
    if (realRows < maxRows + 1) return 'incomplete-wrap'
    if (computeRows > maxRows) return 'collapsible'
    return 'complete-wrap'
  }
  return {
    grid,
    expanded,
    toggle,
    type: takeType(),
  }
}

const QueryForm: React.FC = observer((props) => {
  const { grid, expanded, toggle, type } = useCollapseGrid(1)

  const renderActions = () => {
    return (
      <Fragment>
        <Submit onSubmit={console.log}>Query</Submit>
        <Reset>Reset</Reset>
      </Fragment>
    )
  }

  const renderButtonGroup = () => {
    if (type === 'incomplete-wrap') {
      return (
        <FormButtonGroup.FormItem>
          <FormButtonGroup>{renderActions()}</FormButtonGroup>
        </FormButtonGroup.FormItem>
      )
    }
    if (type === 'collapsible') {
      return (
        <Fragment>
          <FormButtonGroup>
            <a
              href=""
              onClick={(e) => {
                e.preventDefault()
                toggle()
              }}
            >
              {expanded ? 'Fold' : 'UnFold'}
            </a>
          </FormButtonGroup>
          <FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
        </Fragment>
      )
    }
    return (
      <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}>
        {renderActions()}
      </FormButtonGroup>
    )
  }

  return (
    <Form {...props} layout="vertical" feedbackLayout="terse">
      <FormGrid grid={grid}>
        {props.children}
        <FormGrid.GridColumn
          gridSpan={-1}
          style={{ display: 'flex', justifyContent: 'space-between' }}
        >
          {renderButtonGroup()}
        </FormGrid.GridColumn>
      </FormGrid>
    </Form>
  )
})

const SchemaField = createSchemaField({
  components: {
    QueryForm,
    Input,
    Select,
    DatePicker,
    FormItem,
  },
})

export default () => {
  const form = useMemo(() => createForm(), [])
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Object x-component="QueryForm">
          <SchemaField.String
            name="input1"
            title="Input 1"
            x-component="Input"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="input2"
            title="Input 2"
            x-component="Input"
            x-decorator="FormItem"
          />

          <SchemaField.String
            name="select1"
            title="Select 1"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="select2"
            title="Select 2"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="date"
            title="DatePicker"
            x-component="DatePicker"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="dateRange"
            title="DatePicker.RangePicker"
            x-component="DatePicker.RangePicker"
            x-decorator="FormItem"
            x-decorator-props={{
              gridSpan: 2,
            }}
          />
          <SchemaField.String
            name="select3"
            title="Select 3"
            x-component="Select"
            x-decorator="FormItem"
          />
        </SchemaField.Object>
      </SchemaField>
    </FormProvider>
  )
}
```

## API

### FormGrid

| Property name | Type                   | Description                                                                       | Default value     |
| ------------- | ---------------------- | --------------------------------------------------------------------------------- | ----------------- |
| minWidth      | `number \| number[]`   | Minimum element width                                                             | 100               |
| maxWidth      | `number \| number[]`   | Maximum element width                                                             | -                 |
| minColumns    | `number \| number[]`   | Minimum number of columns                                                         | 0                 |
| maxColumns    | `number \| number[]`   | Maximum number of columns                                                         | -                 |
| breakpoints   | number[]               | Container size breakpoints                                                        | `[720,1280,1920]` |
| columnGap     | number                 | Column spacing                                                                    | 8                 |
| rowGap        | number                 | Row spacing                                                                       | 4                 |
| colWrap       | boolean                | Wrap                                                                              | true              |
| strictAutoFit | boolean                | Is width strictly limited by maxWidth                                             | false             |
| shouldVisible | `(node,grid)=>boolean` | Whether to show the current node                                                  | `()=>true`        |
| grid          | `Grid`                 | Grid instance passed in from outside, used to implement more complex layout logic | -                 |

note:

- minWidth takes priority over minColumn
- maxWidth has priority over maxColumn
- The array format of minWidth/maxWidth/minColumns/maxColumns represents the mapping with the breakpoint array

### FormGrid.GridColumn

| Property name | Type   | Description                                                                                                              | Default value |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- |
| gridSpan      | number | The number of columns spanned by the element, if it is -1, it will automatically fill the cell across columns in reverse | 1             |

### FormGrid.createFormGrid

Read the Grid instance from the context

```ts
interface createFormGrid {
  (props: IGridProps): Grid
}
```

- IGridProps reference FormGrid properties
- Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid

### FormGrid.useFormGrid

Read the Grid instance from the context

```ts
interface useFormGrid {
  (): Grid
}
```

- Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid

```

--------------------------------------------------------------------------------
/packages/next/docs/components/FormGrid.md:
--------------------------------------------------------------------------------

```markdown
# FormGrid

> FormGrid component

## Markup Schema example

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/next'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Void
          x-component="FormGrid"
          x-component-props={{
            maxColumns: 3,
            minColumns: 2,
          }}
        >
          <SchemaField.String
            name="aaa"
            title="aaa"
            x-decorator="FormItem"
            x-decorator-props={{ gridSpan: 2 }}
            x-component="Input"
          />
          <SchemaField.String
            name="bbb"
            title="bbb"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ccc"
            title="ccc"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ddd"
            title="ddd"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="eee"
            title="eee"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="fff"
            title="fff"
            x-decorator="FormItem"
            x-component="Input"
          />
          <SchemaField.String
            name="ggg"
            title="ggg"
            x-decorator="FormItem"
            x-component="Input"
          />
        </SchemaField.Void>
      </SchemaField>
    </FormProvider>
  )
}
```

## JSON Schema case

```tsx
import React from 'react'
import { FormItem, Input, FormGrid } from '@formily/next'
import { FormProvider, createSchemaField } from '@formily/react'
import { createForm } from '@formily/core'

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    FormGrid,
  },
})

const form = createForm()

const schema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      'x-component-props': {
        minColumns: [4, 6, 10],
      },
      properties: {
        aaa: {
          type: 'string',
          title: 'AAA',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        bbb: {
          type: 'string',
          title: 'BBB',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ccc: {
          type: 'string',
          title: 'CCC',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ddd: {
          type: 'string',
          title: 'DDD',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        eee: {
          type: 'string',
          title: 'EEE',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        fff: {
          type: 'string',
          title: 'FFF',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
        ggg: {
          type: 'string',
          title: 'GGG',
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
      },
    },
  },
}

export default () => {
  return (
    <FormProvider form={form}>
      <SchemaField schema={schema} />
    </FormProvider>
  )
}
```

## Native case

```tsx
import React from 'react'
import { FormGrid } from '@formily/next'

const { GridColumn } = FormGrid
const Cell = ({ children }) => {
  return (
    <div
      style={{
        backgroundColor: '#AAA',
        color: '#FFF',
        height: 30,
        display: 'flex',
        alignItems: 'center',
        padding: '0 10px',
      }}
    >
      {children}
    </div>
  )
}
export default () => {
  return (
    <React.Fragment>
      <p>maxColumns 3 + minColumns 2</p>
      <FormGrid maxColumns={3} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={4}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxColumns 3</p>
      <FormGrid maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minColumns 2</p>
      <FormGrid minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>Null</p>
      <FormGrid columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>minWidth 150 +maxColumns 3</p>
      <FormGrid minWidth={150} maxColumns={3} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120+minColumns 2</p>
      <FormGrid maxWidth={120} minColumns={2} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>3</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>4</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>5</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>6</Cell>
        </GridColumn>
      </FormGrid>
      <p>maxWidth 120 + gridSpan -1</p>
      <FormGrid maxWidth={120} columnGap={4}>
        <GridColumn gridSpan={2}>
          <Cell>1</Cell>
        </GridColumn>
        <GridColumn>
          <Cell>2</Cell>
        </GridColumn>
        <GridColumn gridSpan={-1}>
          <Cell>3</Cell>
        </GridColumn>
      </FormGrid>
    </React.Fragment>
  )
}
```

## Query Form case

```tsx
import React, { useMemo, Fragment } from 'react'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, observer } from '@formily/react'
import {
  Form,
  Input,
  Select,
  DatePicker,
  FormItem,
  FormGrid,
  Submit,
  Reset,
  FormButtonGroup,
} from '@formily/next'

const useCollapseGrid = (maxRows: number) => {
  const grid = useMemo(
    () =>
      FormGrid.createFormGrid({
        maxColumns: 4,
        maxWidth: 240,
        maxRows: maxRows,
        shouldVisible: (node, grid) => {
          if (node.index === grid.childSize - 1) return true
          if (grid.maxRows === Infinity) return true
          return node.shadowRow < maxRows + 1
        },
      }),
    []
  )
  const expanded = grid.maxRows === Infinity
  const realRows = grid.shadowRows
  const computeRows = grid.fullnessLastColumn
    ? grid.shadowRows - 1
    : grid.shadowRows

  const toggle = () => {
    if (grid.maxRows === Infinity) {
      grid.maxRows = maxRows
    } else {
      grid.maxRows = Infinity
    }
  }
  const takeType = () => {
    if (realRows < maxRows + 1) return 'incomplete-wrap'
    if (computeRows > maxRows) return 'collapsible'
    return 'complete-wrap'
  }
  return {
    grid,
    expanded,
    toggle,
    type: takeType(),
  }
}

const QueryForm: React.FC = observer((props) => {
  const { grid, expanded, toggle, type } = useCollapseGrid(1)

  const renderActions = () => {
    return (
      <Fragment>
        <Submit onSubmit={console.log}>Query</Submit>
        <Reset>Reset</Reset>
      </Fragment>
    )
  }

  const renderButtonGroup = () => {
    if (type === 'incomplete-wrap') {
      return (
        <FormButtonGroup.FormItem>
          <FormButtonGroup>{renderActions()}</FormButtonGroup>
        </FormButtonGroup.FormItem>
      )
    }
    if (type === 'collapsible') {
      return (
        <Fragment>
          <FormButtonGroup>
            <a
              href=""
              onClick={(e) => {
                e.preventDefault()
                toggle()
              }}
            >
              {expanded ? 'Fold' : 'UnFold'}
            </a>
          </FormButtonGroup>
          <FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
        </Fragment>
      )
    }
    return (
      <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}>
        {renderActions()}
      </FormButtonGroup>
    )
  }

  return (
    <Form {...props} layout="vertical" feedbackLayout="terse">
      <FormGrid grid={grid}>
        {props.children}
        <FormGrid.GridColumn
          gridSpan={-1}
          style={{ display: 'flex', justifyContent: 'space-between' }}
        >
          {renderButtonGroup()}
        </FormGrid.GridColumn>
      </FormGrid>
    </Form>
  )
})

const SchemaField = createSchemaField({
  components: {
    QueryForm,
    Input,
    Select,
    DatePicker,
    FormItem,
  },
})

export default () => {
  const form = useMemo(() => createForm(), [])
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.Object x-component="QueryForm">
          <SchemaField.String
            name="input1"
            title="Input 1"
            x-component="Input"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="input2"
            title="Input 2"
            x-component="Input"
            x-decorator="FormItem"
          />

          <SchemaField.String
            name="select1"
            title="Select 1"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="select2"
            title="Select 2"
            x-component="Select"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="date"
            title="DatePicker"
            x-component="DatePicker"
            x-decorator="FormItem"
          />
          <SchemaField.String
            name="dateRange"
            title="DatePicker.RangePicker"
            x-component="DatePicker.RangePicker"
            x-decorator="FormItem"
            x-decorator-props={{
              gridSpan: 2,
            }}
          />
          <SchemaField.String
            name="select3"
            title="Select 3"
            x-component="Select"
            x-decorator="FormItem"
          />
        </SchemaField.Object>
      </SchemaField>
    </FormProvider>
  )
}
```

## API

### FormGrid

| Property name | Type                   | Description                                                                       | Default value     |
| ------------- | ---------------------- | --------------------------------------------------------------------------------- | ----------------- |
| minWidth      | `number \| number[]`   | Minimum element width                                                             | 100               |
| maxWidth      | `number \| number[]`   | Maximum element width                                                             | -                 |
| minColumns    | `number \| number[]`   | Minimum number of columns                                                         | 0                 |
| maxColumns    | `number \| number[]`   | Maximum number of columns                                                         | -                 |
| breakpoints   | number[]               | Container size breakpoints                                                        | `[720,1280,1920]` |
| columnGap     | number                 | Column spacing                                                                    | 8                 |
| rowGap        | number                 | Row spacing                                                                       | 4                 |
| colWrap       | boolean                | Wrap                                                                              | true              |
| strictAutoFit | boolean                | Is width strictly limited by maxWidth                                             | false             |
| shouldVisible | `(node,grid)=>boolean` | Whether to show the current node                                                  | `()=>true`        |
| grid          | `Grid`                 | Grid instance passed in from outside, used to implement more complex layout logic | -                 |

note:

- minWidth takes priority over minColumn
- maxWidth has priority over maxColumn
- The array format of minWidth/maxWidth/minColumns/maxColumns represents the mapping with the breakpoint array

### FormGrid.GridColumn

| Property name | Type   | Description                                                                                                              | Default value |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- |
| gridSpan      | number | The number of columns spanned by the element, if it is -1, it will automatically fill the cell across columns in reverse | 1             |

### FormGrid.createFormGrid

Read the Grid instance from the context

```ts
interface createFormGrid {
  (props: IGridProps): Grid
}
```

- IGridProps reference FormGrid properties
- Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid

### FormGrid.useFormGrid

Read the Grid instance from the context

```ts
interface useFormGrid {
  (): Grid
}
```

- Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid

```

--------------------------------------------------------------------------------
/packages/reactive/src/__tests__/autorun.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { observable, reaction, autorun } from '../'
import { batch } from '../batch'
import { define } from '../model'

const sleep = (duration = 100) =>
  new Promise((resolve) => setTimeout(resolve, duration))

test('autorun', () => {
  const obs = observable({
    aa: {
      bb: 123,
    },
  })
  const handler = jest.fn()
  const dispose = autorun(() => {
    handler(obs.aa.bb)
  })
  obs.aa.bb = 123
  expect(handler).toBeCalledTimes(1)
  obs.aa.bb = 111
  expect(handler).toBeCalledTimes(2)
  dispose()
  obs.aa.bb = 222
  expect(handler).toBeCalledTimes(2)
})

test('reaction', () => {
  const obs = observable({
    aa: {
      bb: 123,
    },
  })
  const handler = jest.fn()
  const dispose = reaction(() => {
    return obs.aa.bb
  }, handler)
  obs.aa.bb = 123
  expect(handler).toBeCalledTimes(0)
  obs.aa.bb = 111
  expect(handler).toBeCalledTimes(1)
  dispose()
  obs.aa.bb = 222
  expect(handler).toBeCalledTimes(1)
})

test('reaction fireImmediately', () => {
  const obs = observable({
    aa: {
      bb: 123,
    },
  })
  const handler = jest.fn()
  const dispose = reaction(
    () => {
      return obs.aa.bb
    },
    handler,
    {
      fireImmediately: true,
    }
  )
  expect(handler).toBeCalledTimes(1)
  obs.aa.bb = 123
  expect(handler).toBeCalledTimes(1)
  obs.aa.bb = 111
  expect(handler).toBeCalledTimes(2)
  dispose()
  obs.aa.bb = 222
  expect(handler).toBeCalledTimes(2)
})

test('reaction untrack handler', () => {
  const obs = observable({
    aa: {
      bb: 123,
      cc: 123,
    },
  })
  const handler = jest.fn()
  const dispose = reaction(
    () => {
      return obs.aa.bb
    },
    () => {
      handler(obs.aa.cc)
    }
  )
  obs.aa.bb = 222
  obs.aa.cc = 222
  expect(handler).toBeCalledTimes(1)
  dispose()
})

test('reaction dirty check', () => {
  const obs: any = {
    aa: 123,
  }
  define(obs, {
    aa: observable.ref,
  })
  const handler = jest.fn()
  reaction(() => {
    return obs.aa
  }, handler)
  batch(() => {
    obs.aa = 123
    obs.aa = 123
  })

  expect(handler).toBeCalledTimes(0)
})

test('reaction with shallow equals', () => {
  const obs: any = {
    aa: { bb: 123 },
  }
  define(obs, {
    aa: observable.ref,
  })
  const handler = jest.fn()
  reaction(() => {
    return obs.aa
  }, handler)
  obs.aa = { bb: 123 }
  expect(handler).toBeCalledTimes(1)
  expect(handler.mock.calls[0][0]).toEqual({ bb: 123 })
})

test('reaction with deep equals', () => {
  const obs: any = {
    aa: { bb: 123 },
  }
  define(obs, {
    aa: observable.ref,
  })
  const handler = jest.fn()
  reaction(
    () => {
      return obs.aa
    },
    handler,
    {
      equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
    }
  )
  obs.aa = { bb: 123 }
  expect(handler).toBeCalledTimes(0)
})

test('autorun direct recursive react', () => {
  const obs = observable<any>({ value: 1 })
  autorun(() => {
    obs.value++
  })
  expect(obs.value).toEqual(2)
})

test('autorun direct recursive react with if', () => {
  const obs1 = observable<any>({})
  const obs2 = observable<any>({})
  const fn = jest.fn()
  autorun(() => {
    if (!obs1.value) {
      obs1.value = '111'
      return
    }
    fn(obs1.value, obs2.value)
  })
  obs2.value = '222'
  expect(fn).toBeCalledTimes(0)
})

test('autorun indirect recursive react', () => {
  const obs1 = observable<any>({})
  const obs2 = observable<any>({})
  const obs3 = observable<any>({})
  autorun(() => {
    obs1.value = obs2.value + 1
  })
  autorun(() => {
    obs2.value = obs3.value + 1
  })
  autorun(() => {
    if (obs1.value) {
      obs3.value = obs1.value + 1
    } else {
      obs3.value = 0
    }
  })
  expect(obs2.value).toEqual(1)
  expect(obs1.value).toEqual(2)
  obs3.value = 1
  expect(obs2.value).toEqual(2)
  expect(obs1.value).toEqual(3)
})

test('autorun indirect alive recursive react', () => {
  const aa = observable<any>({})
  const bb = observable<any>({})
  const cc = observable<any>({})

  batch(() => {
    autorun(() => {
      if (aa.value) {
        bb.value = aa.value + 1
      }
    })
    autorun(() => {
      if (aa.value && bb.value) {
        cc.value = aa.value + bb.value
      }
    })
    batch(() => {
      aa.value = 1
    })
  })
  expect(aa.value).toEqual(1)
  expect(bb.value).toEqual(2)
  expect(cc.value).toEqual(3)
})

test('autorun direct recursive react with head track', () => {
  const obs1 = observable<any>({})
  const obs2 = observable<any>({})
  const fn = jest.fn()
  autorun(() => {
    const obs2Value = obs2.value
    if (!obs1.value) {
      obs1.value = '111'
      return
    }
    fn(obs1.value, obs2Value)
  })
  obs2.value = '222'
  expect(fn).toBeCalledTimes(1)
  expect(fn).lastCalledWith('111', '222')
})

test('autorun.memo', () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  autorun(() => {
    const value = autorun.memo(() => ({
      aa: 0,
    }))
    fn(obs.bb, value.aa++)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(fn).toBeCalledTimes(5)
  expect(fn).nthCalledWith(1, 0, 0)
  expect(fn).nthCalledWith(2, 1, 1)
  expect(fn).nthCalledWith(3, 2, 2)
  expect(fn).nthCalledWith(4, 3, 3)
  expect(fn).nthCalledWith(5, 4, 4)
})

test('autorun.memo with observable', () => {
  const obs1 = observable({
    aa: 0,
  })
  const fn = jest.fn()
  const dispose = autorun(() => {
    const obs2 = autorun.memo(() =>
      observable({
        bb: 0,
      })
    )
    fn(obs1.aa, obs2.bb++)
  })
  obs1.aa++
  obs1.aa++
  obs1.aa++
  expect(fn).toBeCalledTimes(4)
  expect(fn).nthCalledWith(1, 0, 0)
  expect(fn).nthCalledWith(2, 1, 1)
  expect(fn).nthCalledWith(3, 2, 2)
  expect(fn).nthCalledWith(4, 3, 3)
  dispose()
  obs1.aa++
  expect(fn).toBeCalledTimes(4)
})

test('autorun.memo with observable and effect', async () => {
  const obs1 = observable({
    aa: 0,
  })
  const fn = jest.fn()
  const dispose = autorun(() => {
    const obs2 = autorun.memo(() =>
      observable({
        bb: 0,
      })
    )
    fn(obs1.aa, obs2.bb++)
    autorun.effect(() => {
      obs2.bb++
    }, [])
  })
  obs1.aa++
  obs1.aa++
  obs1.aa++
  await sleep(0)
  expect(fn).toBeCalledTimes(5)
  expect(fn).nthCalledWith(1, 0, 0)
  expect(fn).nthCalledWith(2, 1, 1)
  expect(fn).nthCalledWith(3, 2, 2)
  expect(fn).nthCalledWith(4, 3, 3)
  expect(fn).nthCalledWith(5, 3, 5)
  dispose()
  obs1.aa++
  expect(fn).toBeCalledTimes(5)
})

test('autorun.memo with deps', () => {
  const obs = observable<any>({
    bb: 0,
    cc: 0,
  })
  const fn = jest.fn()
  autorun(() => {
    const value = autorun.memo(
      () => ({
        aa: 0,
      }),
      [obs.cc]
    )
    fn(obs.bb, value.aa++)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(fn).toBeCalledTimes(5)
  expect(fn).nthCalledWith(1, 0, 0)
  expect(fn).nthCalledWith(2, 1, 1)
  expect(fn).nthCalledWith(3, 2, 2)
  expect(fn).nthCalledWith(4, 3, 3)
  expect(fn).nthCalledWith(5, 4, 4)
  obs.cc++
  expect(fn).toBeCalledTimes(6)
  expect(fn).nthCalledWith(6, 4, 0)
})

test('autorun.memo with deps and dispose', () => {
  const obs = observable<any>({
    bb: 0,
    cc: 0,
  })
  const fn = jest.fn()
  const dispose = autorun(() => {
    const value = autorun.memo(
      () => ({
        aa: 0,
      }),
      [obs.cc]
    )
    fn(obs.bb, value.aa++)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4, 4)
  obs.cc++
  expect(fn).toBeCalledTimes(6)
  expect(fn).lastCalledWith(4, 0)
  dispose()
  obs.bb++
  obs.cc++
  expect(fn).toBeCalledTimes(6)
})

test('autorun.memo with invalid params', () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  autorun(() => {
    const value = autorun.memo({ aa: 0 } as any)
    fn(obs.bb, value)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4, undefined)
})

test('autorun.memo not in autorun', () => {
  expect(() => autorun.memo(() => ({ aa: 0 }))).toThrow()
})

test('autorun no memo', () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  autorun(() => {
    const value = {
      aa: 0,
    }
    fn(obs.bb, value.aa++)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(fn).toBeCalledTimes(5)
  expect(fn).nthCalledWith(1, 0, 0)
  expect(fn).nthCalledWith(2, 1, 0)
  expect(fn).nthCalledWith(3, 2, 0)
  expect(fn).nthCalledWith(4, 3, 0)
  expect(fn).nthCalledWith(5, 4, 0)
})

test('autorun.effect', async () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  const effect = jest.fn()
  const disposer = jest.fn()
  const dispose = autorun(() => {
    autorun.effect(() => {
      effect()
      return disposer
    }, [])
    fn(obs.bb)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++

  await sleep(0)
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4)
  expect(effect).toBeCalledTimes(1)
  expect(disposer).toBeCalledTimes(0)

  dispose()
  await sleep(0)
  expect(effect).toBeCalledTimes(1)
  expect(disposer).toBeCalledTimes(1)
})

test('autorun.effect dispose when autorun dispose', async () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  const effect = jest.fn()
  const disposer = jest.fn()
  const dispose = autorun(() => {
    autorun.effect(() => {
      effect()
      return disposer
    }, [])
    fn(obs.bb)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++

  dispose()
  await sleep(0)
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4)
  expect(effect).toBeCalledTimes(0)
  expect(disposer).toBeCalledTimes(0)
})

test('autorun.effect with deps', async () => {
  const obs = observable<any>({
    bb: 0,
    cc: 0,
  })
  const fn = jest.fn()
  const effect = jest.fn()
  const dispose = autorun(() => {
    autorun.effect(() => {
      effect()
    }, [obs.cc])
    fn(obs.bb)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(effect).toBeCalledTimes(0)
  await sleep(0)
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4)
  expect(effect).toBeCalledTimes(1)
  obs.cc++
  expect(effect).toBeCalledTimes(1)
  await sleep(0)
  expect(fn).toBeCalledTimes(6)
  expect(fn).lastCalledWith(4)
  expect(effect).toBeCalledTimes(2)
  dispose()
  await sleep(0)
  expect(effect).toBeCalledTimes(2)
})

test('autorun.effect with default deps', async () => {
  const obs = observable<any>({
    bb: 0,
  })
  const fn = jest.fn()
  const effect = jest.fn()
  const dispose = autorun(() => {
    autorun.effect(() => {
      effect()
    })
    fn(obs.bb)
  })
  obs.bb++
  obs.bb++
  obs.bb++
  obs.bb++
  expect(effect).toBeCalledTimes(0)
  await sleep(0)
  expect(fn).toBeCalledTimes(5)
  expect(fn).lastCalledWith(4)
  expect(effect).toBeCalledTimes(5)
  dispose()
  await sleep(0)
  expect(effect).toBeCalledTimes(5)
})

test('autorun.effect not in autorun', () => {
  expect(() => autorun.effect(() => {})).toThrow()
})

test('autorun.effect with invalid params', () => {
  autorun.effect({} as any)
})

test('autorun dispose in batch', () => {
  const obs = observable({
    value: 123,
  })
  const handler = jest.fn()
  const dispose = autorun(() => {
    handler(obs.value)
  })

  batch(() => {
    obs.value = 321
    dispose()
  })
  expect(handler).toBeCalledTimes(1)
})

test('set value by computed depend', () => {
  const obs = observable<any>({})
  const comp1 = observable.computed(() => {
    return obs.aa?.bb
  })
  const comp2 = observable.computed(() => {
    return obs.aa?.cc
  })
  const handler = jest.fn()
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  obs.aa = {
    bb: 123,
    cc: 321,
  }
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, undefined, undefined)
  expect(handler).nthCalledWith(2, 123, 321)
})

test('delete value by computed depend', () => {
  const handler = jest.fn()
  const obs = observable({
    a: {
      b: 1,
      c: 2,
    },
  })
  const comp1 = observable.computed(() => {
    return obs.a?.b
  })
  const comp2 = observable.computed(() => {
    return obs.a?.c
  })
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  delete obs.a
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, 1, 2)
  expect(handler).nthCalledWith(2, undefined, undefined)
})

test('set Set value by computed depend', () => {
  const handler = jest.fn()
  const obs = observable({
    set: new Set(),
  })
  const comp1 = observable.computed(() => {
    return obs.set.has(1)
  })
  const comp2 = observable.computed(() => {
    return obs.set.size
  })
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  obs.set.add(1)
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, false, 0)
  expect(handler).nthCalledWith(2, true, 1)
})

test('delete Set by computed depend', () => {
  const handler = jest.fn()
  const obs = observable({
    set: new Set([1]),
  })
  const comp1 = observable.computed(() => {
    return obs.set.has(1)
  })
  const comp2 = observable.computed(() => {
    return obs.set.size
  })
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  obs.set.delete(1)
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, true, 1)
  expect(handler).nthCalledWith(2, false, 0)
})

test('set Map value by computed depend', () => {
  const handler = jest.fn()
  const obs = observable({
    map: new Map(),
  })
  const comp1 = observable.computed(() => {
    return obs.map.has(1)
  })
  const comp2 = observable.computed(() => {
    return obs.map.size
  })
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  obs.map.set(1, 1)
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, false, 0)
  expect(handler).nthCalledWith(2, true, 1)
})

test('delete Map by computed depend', () => {
  const handler = jest.fn()
  const obs = observable({
    map: new Map([[1, 1]]),
  })
  const comp1 = observable.computed(() => {
    return obs.map.has(1)
  })
  const comp2 = observable.computed(() => {
    return obs.map.size
  })
  autorun(() => {
    handler(comp1.value, comp2.value)
  })
  obs.map.delete(1)
  expect(handler).toBeCalledTimes(2)
  expect(handler).nthCalledWith(1, true, 1)
  expect(handler).nthCalledWith(2, false, 0)
})

test('autorun recollect dependencies', () => {
  const obs = observable<any>({
    aa: 'aaa',
    bb: 'bbb',
    cc: 'ccc',
  })
  const fn = jest.fn()
  autorun(() => {
    fn()
    if (obs.aa === 'aaa') {
      return obs.bb
    }
    return obs.cc
  })
  obs.aa = '111'
  obs.bb = '222'
  expect(fn).toBeCalledTimes(2)
})

test('reaction recollect dependencies', () => {
  const obs = observable<any>({
    aa: 'aaa',
    bb: 'bbb',
    cc: 'ccc',
  })
  const fn1 = jest.fn()
  const fn2 = jest.fn()
  const trigger1 = jest.fn()
  const trigger2 = jest.fn()
  reaction(() => {
    fn1()
    if (obs.aa === 'aaa') {
      return obs.bb
    }
    return obs.cc
  }, trigger1)
  reaction(
    () => {
      fn2()
      if (obs.aa === 'aaa') {
        return obs.bb
      }
      return obs.cc
    },
    trigger2,
    {
      fireImmediately: true,
    }
  )
  obs.aa = '111'
  obs.bb = '222'
  expect(fn1).toBeCalledTimes(2)
  expect(trigger1).toBeCalledTimes(1)
  expect(fn2).toBeCalledTimes(2)
  expect(trigger2).toBeCalledTimes(2)
})

```
Page 22/35FirstPrevNextLast