Skip to content

Commit

Permalink
feat(react): support dynamic scope (#3143)
Browse files Browse the repository at this point in the history
* feat(react): support dynamic scope

* test(shared/reactive): add some tests
  • Loading branch information
janryWang committed May 24, 2022
1 parent e1a2a65 commit 92945b0
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 72 deletions.
4 changes: 2 additions & 2 deletions packages/antd/src/form-button-group/index.tsx
Expand Up @@ -5,14 +5,14 @@
* 4. 鍚稿簳甯冨眬
*/
import React, { useRef, useLayoutEffect, useState } from 'react'
import StickyBox, { StickyBoxCompProps } from 'react-sticky-box'
import { ReactFC } from '@formily/react'
import { Space } from 'antd'
import { SpaceProps } from 'antd/lib/space'
import { BaseItem, IFormItemProps } from '../form-item'
import { usePrefixCls } from '../__builtins__'
import StickyBox from 'react-sticky-box'
import cls from 'classnames'
interface IStickyProps extends StickyBoxCompProps {
interface IStickyProps extends React.ComponentProps<typeof StickyBox> {
align?: React.CSSProperties['textAlign']
}

Expand Down
37 changes: 20 additions & 17 deletions packages/json-schema/src/transformer.ts
Expand Up @@ -7,6 +7,7 @@ import {
isFn,
isPlainObj,
reduce,
lazyMerge,
} from '@formily/shared'
import { Schema } from './schema'
import {
Expand Down Expand Up @@ -99,31 +100,35 @@ const setSchemaFieldState = (
if (target) {
if (request.state) {
field.form.setFieldState(target, (state) =>
patchCompile(state, request.state, {
...scope,
$target: state,
})
patchCompile(
state,
request.state,
lazyMerge(scope, {
$target: state,
})
)
)
}
if (request.schema) {
field.form.setFieldState(target, (state) =>
patchSchemaCompile(
state,
request.schema,
{
...scope,
lazyMerge(scope, {
$target: state,
},
}),
demand
)
)
}
if (isStr(runner) && runner) {
field.form.setFieldState(target, (state) => {
shallowCompile(`{{function(){${runner}}}}`, {
...scope,
$target: state,
})()
shallowCompile(
`{{function(){${runner}}}}`,
lazyMerge(scope, {
$target: state,
})
)()
})
}
} else {
Expand Down Expand Up @@ -153,16 +158,15 @@ const getBaseScope = (
const $self = field
const $form = field.form
const $values = field.form.values
return {
...options.scope,
return lazyMerge(options.scope, {
$form,
$self,
$observable,
$effect,
$memo,
$props,
$values,
}
})
}

const getBaseReactions =
Expand Down Expand Up @@ -194,12 +198,11 @@ const getUserReactions = (
const run = () => {
const $deps = getDependencies(field, reaction.dependencies)
const $dependencies = $deps
const scope = {
...baseScope,
const scope = lazyMerge(baseScope, {
$target: null,
$deps,
$dependencies,
}
})
const compiledWhen = shallowCompile(when, scope)
const condition = when ? compiledWhen : true
const request = condition ? fulfill : otherwise
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/form-button-group/index.tsx
Expand Up @@ -5,13 +5,13 @@
* 4. 鍚稿簳甯冨眬
*/
import React, { useRef, useLayoutEffect, useState } from 'react'
import StickyBox, { StickyBoxCompProps } from 'react-sticky-box'
import StickyBox from 'react-sticky-box'
import { ReactFC } from '@formily/react'
import { Space, ISpaceProps } from '../space'
import { BaseItem, IFormItemProps } from '../form-item'
import { usePrefixCls } from '../__builtins__'
import cls from 'classnames'
interface IStickyProps extends StickyBoxCompProps {
interface IStickyProps extends React.ComponentProps<typeof StickyBox> {
align?: React.CSSProperties['textAlign']
}

Expand Down
5 changes: 4 additions & 1 deletion packages/react/src/components/ExpressionScope.tsx
@@ -1,11 +1,14 @@
import React, { useContext } from 'react'
import { lazyMerge } from '@formily/shared'
import { SchemaExpressionScopeContext } from '../shared'
import { IExpressionScopeProps, ReactFC } from '../types'

export const ExpressionScope: ReactFC<IExpressionScopeProps> = (props) => {
const scope = useContext(SchemaExpressionScopeContext)
return (
<SchemaExpressionScopeContext.Provider value={{ ...scope, ...props.value }}>
<SchemaExpressionScopeContext.Provider
value={lazyMerge(scope, props.value)}
>
{props.children}
</SchemaExpressionScopeContext.Provider>
)
Expand Down
10 changes: 5 additions & 5 deletions packages/react/src/components/ReactiveField.tsx
@@ -1,9 +1,9 @@
import React, { Fragment, useContext } from 'react'
import { toJS } from '@formily/reactive'
import { observer } from '@formily/reactive-react'
import { isFn } from '@formily/shared'
import { FormPath, isFn } from '@formily/shared'
import { isVoidField, GeneralField, Form } from '@formily/core'
import { SchemaOptionsContext } from '../shared'
import { SchemaComponentsContext } from '../shared'
import { RenderPropsChildren } from '../types'
interface IReactiveFieldProps {
field: GeneralField
Expand Down Expand Up @@ -31,7 +31,7 @@ const renderChildren = (
) => (isFn(children) ? children(field, form) : children)

const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
const options = useContext(SchemaOptionsContext)
const components = useContext(SchemaComponentsContext)
if (!props.field) {
return <Fragment>{renderChildren(props.children)}</Fragment>
}
Expand All @@ -47,7 +47,7 @@ const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
return <Fragment>{children}</Fragment>
}
const finalComponent =
options?.getComponent(field.decoratorType) ?? field.decoratorType
FormPath.getIn(components, field.decoratorType) ?? field.decoratorType

return React.createElement(
finalComponent,
Expand Down Expand Up @@ -84,7 +84,7 @@ const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
? field.pattern === 'readOnly'
: undefined
const finalComponent =
options?.getComponent(field.componentType) ?? field.componentType
FormPath.getIn(components, field.componentType) ?? field.componentType
return React.createElement(
finalComponent,
{
Expand Down
19 changes: 3 additions & 16 deletions packages/react/src/components/RecursionField.tsx
@@ -1,12 +1,8 @@
import React, { Fragment, useContext, useRef, useMemo } from 'react'
import React, { Fragment, useContext, useMemo } from 'react'
import { isFn, isValid } from '@formily/shared'
import { GeneralField } from '@formily/core'
import { Schema } from '@formily/json-schema'
import {
SchemaContext,
SchemaOptionsContext,
SchemaExpressionScopeContext,
} from '../shared'
import { SchemaContext, SchemaExpressionScopeContext } from '../shared'
import { IRecursionFieldProps, ReactFC } from '../types'
import { useField } from '../hooks'
import { ObjectField } from './ObjectField'
Expand All @@ -15,18 +11,9 @@ import { Field } from './Field'
import { VoidField } from './VoidField'

const useFieldProps = (schema: Schema) => {
const options = useContext(SchemaOptionsContext)
const scope = useContext(SchemaExpressionScopeContext)
const scopeRef = useRef<any>()
scopeRef.current = scope
return schema.toFieldProps({
...options,
get scope() {
return {
...options.scope,
...scopeRef.current,
}
},
scope,
}) as any
}

Expand Down
31 changes: 12 additions & 19 deletions packages/react/src/components/SchemaField.tsx
Expand Up @@ -6,6 +6,7 @@ import {
SchemaMarkupContext,
SchemaExpressionScopeContext,
SchemaOptionsContext,
SchemaComponentsContext,
} from '../shared'
import {
ReactComponentPath,
Expand All @@ -16,7 +17,7 @@ import {
ISchemaMarkupFieldProps,
ISchemaTypeFieldProps,
} from '../types'
import { FormPath } from '@formily/shared'
import { lazyMerge } from '@formily/shared'
const env = {
nonameId: 0,
}
Expand Down Expand Up @@ -53,25 +54,17 @@ export function createSchemaField<Components extends SchemaReactComponents>(
}

return (
<SchemaOptionsContext.Provider
value={{
...options,
getComponent(name) {
const propsComponent = FormPath.getIn(props.components, name)
if (propsComponent) return propsComponent
return FormPath.getIn(options.components, name)
},
}}
>
<SchemaExpressionScopeContext.Provider
value={{
...options.scope,
...props.scope,
}}
<SchemaOptionsContext.Provider value={options}>
<SchemaComponentsContext.Provider
value={lazyMerge(options.components, props.components)}
>
{renderMarkup()}
{renderChildren()}
</SchemaExpressionScopeContext.Provider>
<SchemaExpressionScopeContext.Provider
value={lazyMerge(options.scope, props.scope)}
>
{renderMarkup()}
{renderChildren()}
</SchemaExpressionScopeContext.Provider>
</SchemaComponentsContext.Provider>
</SchemaOptionsContext.Provider>
)
}
Expand Down
10 changes: 8 additions & 2 deletions packages/react/src/shared/context.ts
@@ -1,7 +1,10 @@
import React, { createContext } from 'react'
import { Form, GeneralField } from '@formily/core'
import { Schema } from '@formily/json-schema'
import { ISchemaFieldOptionContext } from '../types'
import {
ISchemaFieldReactFactoryOptions,
SchemaReactComponents,
} from '../types'

const createContextCleaner = <T>(...contexts: React.Context<T>[]) => {
return ({ children }) => {
Expand All @@ -16,13 +19,16 @@ export const FieldContext = createContext<GeneralField>(null)
export const SchemaMarkupContext = createContext<Schema>(null)
export const SchemaContext = createContext<Schema>(null)
export const SchemaExpressionScopeContext = createContext<any>(null)
export const SchemaComponentsContext =
createContext<SchemaReactComponents>(null)
export const SchemaOptionsContext =
createContext<ISchemaFieldOptionContext>(null)
createContext<ISchemaFieldReactFactoryOptions>(null)

export const ContextCleaner = createContextCleaner(
FieldContext,
SchemaMarkupContext,
SchemaContext,
SchemaExpressionScopeContext,
SchemaComponentsContext,
SchemaOptionsContext
)
3 changes: 1 addition & 2 deletions packages/react/src/types.ts
Expand Up @@ -71,8 +71,7 @@ export interface ISchemaFieldReactFactoryOptions<
}

export interface ISchemaFieldOptionContext {
getComponent: (name: string) => JSXComponent
scope?: any
components: SchemaReactComponents
}

export interface ISchemaFieldProps<
Expand Down
56 changes: 55 additions & 1 deletion packages/reactive/src/__tests__/annotations.spec.ts
@@ -1,4 +1,4 @@
import { observable, action, model } from '../'
import { observable, action, model, define } from '../'
import { autorun, reaction } from '../autorun'
import { observe } from '../observe'
import { isObservable } from '../externals'
Expand Down Expand Up @@ -292,3 +292,57 @@ test('computed no track get', () => {
expect(compu.value).toBe(123)
})
})

test('computed cache descriptor', () => {
class A {
_value = 0
constructor() {
define(this, {
_value: observable.ref,
value: observable.computed,
})
}

get value() {
return this._value
}
}
const obs1 = new A()
const obs2 = new A()
const handler1 = jest.fn()
const handler2 = jest.fn()
autorun(() => {
handler1(obs1.value)
})
autorun(() => {
handler2(obs2.value)
})
expect(handler1).toBeCalledTimes(1)
expect(handler2).toBeCalledTimes(1)
obs1._value = 123
obs2._value = 123
expect(handler1).toBeCalledTimes(2)
expect(handler2).toBeCalledTimes(2)
})

test('computed normal object', () => {
const obs = define(
{
_value: 0,
get value() {
return this._value
},
},
{
_value: observable.ref,
value: observable.computed,
}
)
const handler = jest.fn()
autorun(() => {
handler(obs.value)
})
expect(handler).toBeCalledTimes(1)
obs._value = 123
expect(handler).toBeCalledTimes(2)
})
20 changes: 20 additions & 0 deletions packages/reactive/src/__tests__/define.spec.ts
Expand Up @@ -138,6 +138,26 @@ describe('makeObservable', () => {
expect(handler).toBeCalledTimes(2)
expect(target.cc).toEqual(44)
})
test('unexpect target', () => {
const testFn = jest.fn()
const testArr = []
const obs1 = define(4 as any, {
value: observable.computed,
})
const obs2 = define('123' as any, {
value: observable.computed,
})
const obs3 = define(testFn as any, {
value: observable.computed,
})
const obs4 = define(testArr as any, {
value: observable.computed,
})
expect(obs1).toBe(4)
expect(obs2).toBe('123')
expect(obs3).toBe(testFn)
expect(obs4).toBe(testArr)
})
})

test('define model', () => {
Expand Down

0 comments on commit 92945b0

Please sign in to comment.