Skip to content

Commit

Permalink
feat(react): improve expression scopes (#2778)
Browse files Browse the repository at this point in the history
  • Loading branch information
janryWang authored Jan 20, 2022
1 parent ffbaba2 commit 25c4897
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 16 deletions.
16 changes: 14 additions & 2 deletions packages/antd/src/array-base/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon'
import { ButtonProps } from 'antd/lib/button'
import { ArrayField } from '@formily/core'
import { useField, useFieldSchema, Schema, JSXComponent } from '@formily/react'
import {
useField,
useFieldSchema,
Schema,
JSXComponent,
ExpressionScope,
} from '@formily/react'
import { isValid, clone, isBool } from '@formily/shared'
import { SortableHandle } from 'react-sortable-hoc'
import { usePrefixCls } from '../__builtins__'
Expand Down Expand Up @@ -102,7 +108,13 @@ export const ArrayBase: ComposedArrayBase = (props) => {
}

ArrayBase.Item = ({ children, ...props }) => {
return <ItemContext.Provider value={props}>{children}</ItemContext.Provider>
return (
<ItemContext.Provider value={props}>
<ExpressionScope value={{ $record: props.record, $index: props.index }}>
{children}
</ExpressionScope>
</ItemContext.Provider>
)
}

const SortHandle = SortableHandle((props: any) => {
Expand Down
16 changes: 14 additions & 2 deletions packages/next/src/array-base/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { Button } from '@alifd/next'
import { isValid, clone, isBool } from '@formily/shared'
import { ButtonProps } from '@alifd/next/lib/button'
import { ArrayField } from '@formily/core'
import { useField, useFieldSchema, Schema, JSXComponent } from '@formily/react'
import {
useField,
useFieldSchema,
Schema,
JSXComponent,
ExpressionScope,
} from '@formily/react'
import { SortableHandle } from 'react-sortable-hoc'
import {
usePrefixCls,
Expand Down Expand Up @@ -102,7 +108,13 @@ export const ArrayBase: ComposedArrayBase = (props) => {
}

ArrayBase.Item = ({ children, ...props }) => {
return <ItemContext.Provider value={props}>{children}</ItemContext.Provider>
return (
<ItemContext.Provider value={props}>
<ExpressionScope value={{ $record: props.record, $index: props.index }}>
{children}
</ExpressionScope>
</ItemContext.Provider>
)
}

const SortHandle = SortableHandle((props: any) => {
Expand Down
Empty file.
62 changes: 62 additions & 0 deletions packages/react/docs/api/components/ExpressionScope.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
order: 8
---

# ExpressionScope

## 描述

用于自定义组件内部给 json-schema 表达式传递局部作用域

## 签名

```ts
interface IExpressionScopeProps {
value?: any
}
type ExpressionScope = React.FC<IExpressionScopeProps>
```
## 用例
```tsx
import React from 'react'
import { createForm } from '@formily/core'
import {
FormProvider,
createSchemaField,
ExpressionScope,
} from '@formily/react'
import { Input } from 'antd'

const form = createForm()

const Container = (props) => {
return (
<ExpressionScope value={{ $innerScope: 'this inner scope value' }}>
{props.children}
</ExpressionScope>
)
}

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

export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void x-component="Container">
<SchemaField.String
name="input"
x-component="Input"
x-value="{{$innerScope}}"
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
)
```
39 changes: 39 additions & 0 deletions packages/react/src/__tests__/expression.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import { render } from '@testing-library/react'
import { createForm } from '@formily/core'
import { FormProvider, ExpressionScope, createSchemaField } from '..'

test('expression scope', async () => {
const Container = (props) => {
return (
<ExpressionScope value={{ $innerScope: 'this is inner scope value' }}>
{props.children}
</ExpressionScope>
)
}
const Input = (props) => <div data-testid="test-input">{props.value}</div>
const SchemaField = createSchemaField({
components: {
Container,
Input,
},
})
const form = createForm()
const { getByTestId } = render(
<FormProvider form={form}>
<SchemaField scope={{ $outerScope: 'this is outer scope value' }}>
<SchemaField.Void x-component="Container">
<SchemaField.String
name="input"
x-component="Input"
x-value="{{$innerScope + ' ' + $outerScope}}"
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
)

expect(getByTestId('test-input').textContent).toBe(
'this is inner scope value this is outer scope value'
)
})
30 changes: 18 additions & 12 deletions packages/react/src/__tests__/field.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,51 +237,57 @@ test('connect', async () => {
})

test('fields unmount and validate', async () => {
const fn = jest.fn();
const fn = jest.fn()
const form = createForm({
initialValues: {
parent: {
type: 'mounted',
}
},
},
effects: () => {
onFieldUnmount('parent.child', () => {
fn()
})
}
},
})
const Parent = observer(() => {
const field = useField()
const field = useField<FieldType>()
if (field.value.type === 'mounted') {
return <Field name='child' component={[Input]} validator={{required: true}} />
return (
<Field
name="child"
component={[Input]}
validator={{ required: true }}
/>
)
}
return <div data-testid='unmounted'></div>
return <div data-testid="unmounted"></div>
})
act(async () => {
const MyComponent = () => {
return (
<FormProvider form={form}>
<Field name='parent' component={[Parent]} />
<Field name="parent" component={[Parent]} />
</FormProvider>
)
}
render(<MyComponent />)
try {
await form.validate()
} catch {}

expect(form.invalid).toBeTruthy()

form.query('parent').take((field) => {
field.setState((state) => {
state.value.type = 'unmounted'
})
})

await waitFor(() => {
expect(fn.mock.calls.length).toBe(1)
});
})
await form.validate()
expect(form.invalid).toBeFalsy()
})
})
})
12 changes: 12 additions & 0 deletions packages/react/src/components/ExpressionScope.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { useContext } from 'react'
import { SchemaExpressionScopeContext } from '../shared'
import { IExpressionScopeProps } from '../types'

export const ExpressionScope: React.FC<IExpressionScopeProps> = (props) => {
const scope = useContext(SchemaExpressionScopeContext)
return (
<SchemaExpressionScopeContext.Provider value={{ ...scope, ...props.value }}>
{props.children}
</SchemaExpressionScopeContext.Provider>
)
}
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export * from './ArrayField'
export * from './ObjectField'
export * from './VoidField'
export * from './RecursionField'
export * from './ExpressionScope'
export * from './SchemaField'
export * from './Field'
4 changes: 4 additions & 0 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,7 @@ export type ISchemaTypeFieldProps<
Decorator extends ReactComponentPath<Components>,
Component extends ReactComponentPath<Components>
> = ISchemaMarkupFieldProps<Components, Decorator, Component>

export interface IExpressionScopeProps {
value?: any
}

0 comments on commit 25c4897

Please sign in to comment.