Skip to content

Commit

Permalink
refactor(vue): change Field component type to functional (#2773)
Browse files Browse the repository at this point in the history
* refactor(vue): change Field component type to functional

* feat(vue): add vue3 branch ignore test
  • Loading branch information
MisicDemone authored Jan 20, 2022
1 parent 064e13a commit ffbaba2
Show file tree
Hide file tree
Showing 19 changed files with 733 additions and 686 deletions.
6 changes: 3 additions & 3 deletions packages/reactive-vue/src/hooks/useObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export const useObserver = (options?: IObserverOptions) => {
set(newValue) {
disposeTracker()

const update = () => tracker.track(newValue)
const update = () => tracker?.track(newValue)

tracker = new Tracker(() => {
if (options?.scheduler) {
options?.scheduler?.(update)
if (options?.scheduler && typeof options.scheduler === 'function') {
options.scheduler(update)
} else {
update()
}
Expand Down
7 changes: 5 additions & 2 deletions packages/reactive-vue/src/observer/observerInVue2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ function observer(Component: any, observerOptions?: IObserverOptions): any {
}

const tracker = new Tracker(() => {
if (observerOptions?.scheduler) {
observerOptions?.scheduler?.(reactiveRender)
if (
observerOptions?.scheduler &&
typeof observerOptions.scheduler === 'function'
) {
observerOptions.scheduler(reactiveRender)
} else {
reactiveRender()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/docs/demos/api/components/array-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<template #default="{ field }">
<div
v-for="(item, index) in field.value || []"
:key="item.id"
:key="`${item.id}-${index}`"
:style="{ marginBottom: '10px' }"
>
<Space>
Expand Down
19 changes: 11 additions & 8 deletions packages/vue/src/__tests__/field.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import Vue, { FunctionalComponentOptions } from 'vue'
import { render, fireEvent, waitFor } from '@testing-library/vue'
import { defineComponent, h } from '@vue/composition-api'
import { createForm, Field as FieldType } from '@formily/core'
import {
createForm,
Field as FieldType,
isField,
isVoidField,
onFieldChange,
} from '@formily/core'
import { useField, useFormEffects, connect, mapProps, mapReadPretty } from '../'
import {
FormProvider,
Expand All @@ -12,7 +18,6 @@ import {
} from '../vue2-components'
import ReactiveField from '../components/ReactiveField'
// import { expectThrowError } from './shared'
import { isField, isVoidField, onFieldChange } from '@formily/core'

Vue.component('FormProvider', FormProvider)
Vue.component('ArrayField', ArrayField)
Expand Down Expand Up @@ -205,10 +210,10 @@ test('render field with html attrs', async () => {

test('ReactiveField', () => {
render({
template: `<ReactiveField :field="null" />`,
template: `<ReactiveField />`,
})
render({
template: `<ReactiveField :field="null">
template: `<ReactiveField>
<div></div>
</ReactiveField>`,
})
Expand Down Expand Up @@ -371,23 +376,21 @@ test('connect', async () => {

const form = createForm()
const { queryByText, getByTestId } = render({
components: {
CustomFormItem,
},
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]" />
<CustomFormItem>dd</CustomFormItem>
<component :is="CustomFormItem">dd</component>
</FormProvider>`,
})
form.query('aa').take((field) => {
Expand Down
122 changes: 110 additions & 12 deletions packages/vue/src/__tests__/schema.json.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { createForm } from '@formily/core'
import { observer } from '@formily/reactive-vue'
import { Schema } from '@formily/json-schema'
import { render, waitFor } from '@testing-library/vue'
import { mount } from '@vue/test-utils'
import Vue, { FunctionalComponentOptions } from 'vue'
import { FormProvider, createSchemaField } from '../vue2-components'
import { connect, mapProps, mapReadPretty } from '../'
import { defineComponent } from 'vue-demi'
import {
FormProvider,
createSchemaField,
RecursionField,
} from '../vue2-components'
import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '../'
import { defineComponent, h } from 'vue-demi'

Vue.component('FormProvider', FormProvider)

Expand Down Expand Up @@ -41,6 +46,26 @@ const Input2: FunctionalComponentOptions = {
},
}

const ArrayItems = observer(
defineComponent({
setup() {
const fieldRef = useField()
const schemaRef = useFieldSchema()

return () => {
const field = fieldRef.value
const schema = schemaRef.value
const items = field.value?.map?.((item, index) => {
return h(RecursionField, {
props: { schema: schema.items, name: index },
})
})
return h('div', { attrs: { 'data-testid': 'array-items' } }, [items])
}
},
})
)

const Previewer: FunctionalComponentOptions = {
functional: true,
render(h, context) {
Expand Down Expand Up @@ -969,7 +994,7 @@ describe('expression', () => {
})

describe('schema controlled', () => {
test('view updated with schema', async () => {
test('view update correctly when schema changed', async () => {
const form = createForm({})
const { SchemaField } = createSchemaField({
components: {
Expand Down Expand Up @@ -999,7 +1024,6 @@ describe('schema controlled', () => {
},
methods: {
changeSchema() {
this.form = createForm()
this.schema = {
type: 'object',
properties: {
Expand All @@ -1018,13 +1042,87 @@ describe('schema controlled', () => {
<button @click="changeSchema()">changeSchema</button>
</FormProvider>`,
})
const wrapper = mount(component, { attachToDocument: true })
const { queryByTestId, getByText } = render(component)

expect(wrapper.contains('.input')).toBe(true)
expect(wrapper.contains('.input2')).toBe(true)
await wrapper.find('button').trigger('click')
expect(wrapper.contains('.input2')).toBe(true)
expect(wrapper.contains('.input')).toBe(false)
wrapper.destroy()
expect(queryByTestId('input')).toBeVisible()
expect(queryByTestId('input2')).toBeVisible()
getByText('changeSchema').click()
await waitFor(() => {
expect(queryByTestId('input2')).toBeVisible()
expect(queryByTestId('input')).toBeNull()
})
})
test('view updated correctly with schema fragment changed', async () => {
const form = createForm({})
const { SchemaField } = createSchemaField({
components: {
Input,
Input2,
ArrayItems,
},
})
const frag1 = {
type: 'object',
properties: {
input1: {
type: 'string',
'x-component': 'Input',
},
},
}
const frag2 = {
type: 'array',
'x-component': 'ArrayItems',
items: {
type: 'object',
properties: {
input2: {
type: 'string',
'x-component': 'Input2',
},
},
},
}
const component = defineComponent({
components: { SchemaField },
data() {
return {
form,
schema: {
type: 'object',
properties: {
input: frag1,
},
},
}
},
methods: {
changeSchema() {
this.form.clearFormGraph('input')
this.form.deleteValuesIn('input')
this.schema = {
type: 'object',
properties: {
input: frag2,
},
}
},
},
template: `<FormProvider :form="form">
<SchemaField
:schema="schema"
/>
<button @click="changeSchema()">changeSchema</button>
</FormProvider>`,
})
const { queryByTestId, getByText } = render(component)

expect(queryByTestId('input')).toBeVisible()
expect(queryByTestId('array-items')).toBeNull()
getByText('changeSchema').click()
await waitFor(() => {
expect(queryByTestId('input')).toBeNull()
expect(queryByTestId('array-items')).toBeVisible()
})
})
})
112 changes: 42 additions & 70 deletions packages/vue/src/components/ArrayField.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,53 @@
import { provide, defineComponent, watch, computed } from 'vue-demi'
import { useField, useForm } from '../hooks'
import { useAttach } from '../hooks/useAttach'
import { isVue2, h as _h } from 'vue-demi'
import ReactiveField from './ReactiveField'
import { FieldSymbol } from '../shared/context'
import h from '../shared/h'
import { getRawComponent } from '../utils/getRawComponent'

import type { IArrayFieldProps, DefineComponent } from '../types'
import { getFieldProps } from '../utils/getFieldProps'

export default defineComponent({
name: 'ArrayField',
props: [
'name',
'basePath',
'title',
'description',
'value',
'initialValue',
'required',
'display',
'pattern',
'hidden',
'visible',
'editable',
'disabled',
'readOnly',
'readPretty',
'dataSource',
'validateFirst',
'validator',
'decorator',
'component',
'reactions',
'content',
'data',
],
setup(props: IArrayFieldProps, { slots }) {
const formRef = useForm()
const parentRef = useField()
let ArrayField: DefineComponent<IArrayFieldProps>

const basePath = computed(() =>
props.basePath !== undefined ? props.basePath : parentRef?.value?.address
)
const createField = () =>
formRef.value.createArrayField({
...props,
basePath: basePath.value,
...getRawComponent(props),
})
const [fieldRef, checker] = useAttach(createField())
watch(
() => props,
() => (fieldRef.value = checker(createField())),
{ deep: true }
)
watch([formRef, parentRef], () => (fieldRef.value = checker(createField())))

provide(FieldSymbol, fieldRef)

return () => {
const field = fieldRef.value
/* istanbul ignore else */
if (isVue2) {
ArrayField = {
functional: true,
name: 'ArrayField',
props: getFieldProps(),
render(h, context) {
const props = context.props as IArrayFieldProps
const attrs = context.data.attrs
const componentData = {
...context.data,
props: {
field,
fieldType: 'ArrayField',
fieldProps: {
...attrs,
...props,
...getRawComponent(props),
},
},
}
const children = {
...slots,
return _h(ReactiveField, componentData, context.children)
},
} as unknown as DefineComponent<IArrayFieldProps>
} else {
ArrayField = {
name: 'ArrayField',
props: getFieldProps(),
setup(props: IArrayFieldProps, context) {
return () => {
const componentData = {
fieldType: 'ArrayField',
fieldProps: {
...props,
...getRawComponent(props),
},
} as any
const slots = context.slots as any
return _h(ReactiveField, componentData, slots)
}
if (slots.default) {
children.default = () =>
slots.default({
field: field,
form: field.form,
})
}
return h(ReactiveField, componentData, children)
}
},
}) as DefineComponent<IArrayFieldProps>
},
} as unknown as DefineComponent<IArrayFieldProps>
}

export default ArrayField
Loading

0 comments on commit ffbaba2

Please sign in to comment.