Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: multi source update #3892

Open
wants to merge 14 commits into
base: formily_next
Choose a base branch
from
133 changes: 1 addition & 132 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,132 +1 @@
---
title: Formily - Alibaba unified front-end form solution
order: 10
hero:
title: Alibaba Formily
desc: Alibaba Unified Front-end Form Solution
actions:
- text: Introduction
link: /guide
- text: Quick start
link: /guide/quick-start
features:
- icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg
title: Easier to Use
desc: Out of the box, rich cases
- icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg
title: More Efficient
desc: Fool writing, ultra-high performance
- icon: https://img.alicdn.com/imgextra/i3/O1CN01xlETZk1G0WSQT6Xii_!!6000000000560-55-tps-800-800.svg
title: More Professional
desc: Complete, flexible and elegant
footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self
---

```tsx
/**
* inline: true
*/
import React from 'react'
import { Section } from './site/Section'
import './site/styles.less'

export default () => (
<Section
title="Fool Writing, Ultra-high Performance"
style={{ marginTop: 40 }}
titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }}
>
<iframe
className="codesandbox"
src="https://codesandbox.io/embed/formilyyaliceshi-vbu4w?fontsize=12&module=%2FApp.tsx&theme=dark"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</Section>
)
```

```tsx
/**
* inline: true
*/
import React from 'react'
import { Section } from './site/Section'
import './site/styles.less'

export default () => (
<Section
title="Form Builder,Efficient Development"
style={{ marginTop: 140, fontWeight: 'bold' }}
titleStyle={{ paddingBottom: 140 }}
scale={1.2}
>
<a href="//designable-antd.formilyjs.org" target="_blank" rel="noreferrer">
<img src="//img.alicdn.com/imgextra/i2/O1CN01eI9FLz22tZek2jv7E_!!6000000007178-2-tps-3683-2272.png" />
</a>
</Section>
)
```

```tsx
/**
* inline: true
*/
import React from 'react'
import { Section } from './site/Section'
import './site/styles.less'

export default () => (
<Section
title="Pure Core, More Extensibility"
style={{ marginTop: 140 }}
titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }}
>
<a href="//core.formilyjs.org" target="_blank" rel="noreferrer">
<img src="//img.alicdn.com/imgextra/i4/O1CN019qbf1b1ChnTfT9x3X_!!6000000000113-55-tps-1939-1199.svg" />
</a>
</Section>
)
```

```tsx
/**
* inline: true
*/
import React from 'react'
import { Section } from './site/Section'
import { Contributors } from './site/Contributors'
import './site/styles.less'

export default () => (
<Section
title="Active Community & Genius People"
style={{ marginTop: 100 }}
titleStyle={{ paddingBottom: 140, fontWeight: 'bold' }}
>
<Contributors />
</Section>
)
```

```tsx
/**
* inline: true
*/
import React from 'react'
import { Section } from './site/Section'
import { QrCode, QrCodeGroup } from './site/QrCode'
import './site/styles.less'

export default () => (
<Section
title="High-Quality Community Group"
style={{ marginTop: 140 }}
titleStyle={{ paddingBottom: 20, fontWeight: 'bold' }}
>
<QrCodeGroup>
<QrCode link="//img.alicdn.com/imgextra/i1/O1CN011zlc5b1uu1BDUpNg1_!!6000000006096-2-tps-978-1380.png" />
</QrCodeGroup>
</Section>
)
```
<code src="./index" />
98 changes: 98 additions & 0 deletions docs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'

import { autorun, batch, reaction, observable } from '@formily/reactive'

const fieldA = observable({
value: '',
visible: true,
})

const fieldB = observable({
value: '',
visible: true,
cache: '',
})

const fieldC = observable({
value: '',
visible: true,
cache: '',
})

// ===== fieldB reaction =====
reaction(
() => fieldB.value,
() => {
if (fieldB.value && fieldB.visible === false) {
fieldB.cache = fieldB.value
// 删除 fieldB.value 时,会重新 runReaction
delete fieldB.value
}
}
)

reaction(
() => fieldB.visible,
() => {
if (fieldB.visible === true) {
// debugger
console.log('fieldB.cache: ', fieldB.cache)
// 执行到这里时,不会执行 fieldB.value 的 autorun,因为在上面 delete fieldB.value 时,已经执行了
fieldB.value = fieldB.cache
}
}
)

// ===== fieldC reaction =====
reaction(
() => fieldC.value,
() => {
if (fieldC.value && fieldC.visible === false) {
fieldC.cache = fieldC.value
delete fieldC.value
}
}
)

reaction(
() => fieldC.visible,
() => {
if (fieldC.visible === true) {
fieldC.value = fieldC.cache
}
}
)

// ===== schema 渲染 =====
autorun(() => {
fieldB.visible = fieldA.value === 'fieldA'
}, 'A')

autorun(() => {
fieldC.visible = fieldB.value === 'fieldB'
}, 'B')

// fieldB.value = 'fieldB'
// fieldC.value = 'fieldC'
// fieldA.value = 'fieldA'

batch(() => {
fieldB.value = 'fieldB'
fieldC.value = 'fieldC'
// debugger
fieldA.value = 'fieldA'
// window.xxx = true
})

console.log(
'fieldA.visible:',
fieldA.visible,
'fieldB.visible:',
fieldB.visible,
'fieldC.visible:',
fieldC.visible
)

const App = () => <div>123123</div>

export default App
116 changes: 116 additions & 0 deletions packages/reactive/src/__tests__/autorun.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ test('autorun', () => {
expect(handler).toBeCalledTimes(2)
})

test('autorun first argument is not a function', () => {
autorun({} as any)
autorun(1 as any)
autorun('1' as any)
})

test('reaction', () => {
const obs = observable({
aa: {
Expand Down Expand Up @@ -741,3 +747,113 @@ test('reaction recollect dependencies', () => {
expect(fn2).toBeCalledTimes(2)
expect(trigger2).toBeCalledTimes(2)
})

test('multiple source update', () => {
const obs = observable<any>({})

const fn1 = jest.fn()
const fn2 = jest.fn()

autorun(() => {
const A = obs.A
const B = obs.B
if (A !== undefined && B !== undefined) {
obs.C = A / B
fn1()
}
})

autorun(() => {
const C = obs.C
const B = obs.B
if (C !== undefined && B !== undefined) {
obs.D = C * B
fn2()
}
})

obs.A = 1
obs.B = 2

expect(fn1).toBeCalledTimes(1)
expect(fn2).toBeCalledTimes(1)
})

test('same source in nest update', () => {
const obs = observable<any>({})

const fn1 = jest.fn()

autorun(() => {
const B = obs.B
obs.B = 'B'
fn1()
return B
})

obs.B = 'B2'

expect(fn1).toBeCalledTimes(2)
})

test('repeat execute autorun cause by deep indirect dependency', () => {
const obs: any = observable({ aa: 1, bb: 1, cc: 1 })
const fn = jest.fn()
const fn2 = jest.fn()
const fn3 = jest.fn()
autorun(() => fn((obs.aa = obs.bb + obs.cc)))
autorun(() => fn2((obs.bb = obs.aa + obs.cc)))
autorun(() => fn3((obs.cc = obs.aa + obs.bb)))

expect(fn).toBeCalledTimes(4)
expect(fn2).toBeCalledTimes(4)
expect(fn3).toBeCalledTimes(3)
})

test('batch execute autorun cause by deep indirect dependency', () => {
const obs: any = observable({ aa: 1, bb: 1, cc: 1 })
const fn = jest.fn()
const fn2 = jest.fn()
const fn3 = jest.fn()
autorun(() => fn((obs.aa = obs.bb + obs.cc)))
autorun(() => fn2((obs.bb = obs.aa + obs.cc)))
autorun(() => fn3((obs.cc = obs.aa + obs.bb)))

expect(fn).toBeCalledTimes(4)
expect(fn2).toBeCalledTimes(4)
expect(fn3).toBeCalledTimes(3)

fn.mockClear()
fn2.mockClear()
fn3.mockClear()

batch(() => {
obs.aa = 100
obs.bb = 100
obs.cc = 100
})

expect(fn).toBeCalledTimes(2)
expect(fn2).toBeCalledTimes(2)
expect(fn3).toBeCalledTimes(2)
})

test('multiple update should trigger only one', () => {
const obs = observable({ aa: 1, bb: 1 })

autorun(() => {
obs.aa = obs.bb + 1
obs.bb = obs.aa + 1
})

expect(obs.aa).toBe(2)
expect(obs.bb).toBe(3)

autorun(() => {
obs.aa = obs.bb + 1
obs.bb = obs.aa + 1
})

expect(obs.aa).toBe(6)
expect(obs.bb).toBe(7)
})