Skip to content

Commit

Permalink
refactor(reactive): fix #1598 and support #1586 and super performance…
Browse files Browse the repository at this point in the history
… optimization
  • Loading branch information
janryWang committed Jun 18, 2021
1 parent c711176 commit a1e7200
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 182 deletions.
1 change: 1 addition & 0 deletions packages/reactive/docs/api/reaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
interface IReactionOptions<T> {
name?: string
equals?: (oldValue: T, newValue: T) => boolean //脏检查
fireImmediately?: boolean //是否第一次默认触发,绕过脏检查
}

interface reaction<T> {
Expand Down
25 changes: 25 additions & 0 deletions packages/reactive/src/__tests__/autorun.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,31 @@ test('reaction', () => {
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,
}
)
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 dirty check', () => {
const obs: any = {
aa: 123,
Expand Down
2 changes: 1 addition & 1 deletion packages/reactive/src/__tests__/externals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

test('is support observable', () => {
const obs = observable<any>({ aa: 111 })
expect(isSupportObservable(obs)).toBeFalsy()
expect(isSupportObservable(obs)).toBeTruthy()
expect(isSupportObservable(null)).toBeFalsy()
expect(isSupportObservable([])).toBeTruthy()
expect(isSupportObservable({})).toBeTruthy()
Expand Down
80 changes: 47 additions & 33 deletions packages/reactive/src/__tests__/observe.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { observable, observe } from '../'
import { ProxyRaw, RawNode } from '../environment'

const getObservers = (target: any) => {
return RawNode.get(ProxyRaw.get(target))?.observers
}

const getDeepObservers = (target: any) => {
return RawNode.get(ProxyRaw.get(target))?.deepObservers
}
//import { ProxyRaw, RawNode } from '../environment'

test('deep observe', () => {
const obs = observable<any>({
Expand Down Expand Up @@ -45,30 +37,30 @@ test('shallow observe', () => {
expect(handler).toHaveBeenCalledTimes(2)
})

test('auto dispose observe', () => {
const obs = observable<any>({
aa: {
bb: {
cc: [11, 22, 33],
},
},
})
const handler = jest.fn()
observe(obs, handler)
observe(obs.aa, handler)
observe(obs.aa.bb, handler)
expect(getDeepObservers(obs.aa).length).toEqual(1)
expect(getDeepObservers(obs.aa.bb).length).toEqual(1)
obs.aa.bb = { kk: 'mm' }
expect(getDeepObservers(obs.aa.bb).length).toEqual(1)
expect(getDeepObservers(obs.aa).length).toEqual(1)
expect(getObservers(obs.aa).length).toEqual(0)
expect(handler).toBeCalledTimes(3)
observe(obs.aa, handler)
expect(getObservers(obs.aa).length).toEqual(0)
expect(getDeepObservers(obs.aa).length).toEqual(2)
delete obs.aa
})
// test('auto dispose observe', () => {
// const obs = observable<any>({
// aa: {
// bb: {
// cc: [11, 22, 33],
// },
// },
// })
// const handler = jest.fn()
// observe(obs, handler)
// observe(obs.aa, handler)
// observe(obs.aa.bb, handler)
// expect(getDeepObservers(obs.aa).length).toEqual(1)
// expect(getDeepObservers(obs.aa.bb).length).toEqual(1)
// obs.aa.bb = { kk: 'mm' }
// expect(getDeepObservers(obs.aa.bb).length).toEqual(1)
// expect(getDeepObservers(obs.aa).length).toEqual(1)
// expect(getObservers(obs.aa).length).toEqual(0)
// expect(handler).toBeCalledTimes(3)
// observe(obs.aa, handler)
// expect(getObservers(obs.aa).length).toEqual(0)
// expect(getDeepObservers(obs.aa).length).toEqual(2)
// delete obs.aa
// })

test('root replace observe', () => {
const obs = observable<any>({
Expand Down Expand Up @@ -138,3 +130,25 @@ test('dispose observe', () => {
obs.aa = { mm: 444 }
expect(handler).toBeCalledTimes(4)
})

test('array delete', () => {
const array = observable([{ value: 1 }, { value: 2 }])

const fn = jest.fn()

const dispose = observe(array, (change) => {
if (change.type === 'set' && change.key === 'value') {
fn(change.path?.join('.'))
}
})

array[0].value = 3
expect(fn.mock.calls[0][0]).toBe('0.value')

array.splice(0, 1)

array[0].value = 3
expect(fn.mock.calls[1][0]).toBe('0.value')

dispose()
})
5 changes: 4 additions & 1 deletion packages/reactive/src/autorun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export const reaction = <T>(
return autorun(() => {
value.currentValue = tracker()
dirty.current = dirtyCheck()
if (dirty.current && tracked.current) {
if (
(dirty.current && tracked.current) ||
(!tracked.current && realOptions.fireImmediately)
) {
untracked(() => {
if (isFn(subscriber)) subscriber(value.currentValue)
})
Expand Down
3 changes: 3 additions & 0 deletions packages/reactive/src/checkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ export const isCollectionType = (target: any) => {
isMap(target) || isWeakMap(target) || isSet(target) || isWeakSet(target)
)
}
export const isNormalType = (target: any) => {
return isPlainObj(target) || isArr(target)
}
77 changes: 56 additions & 21 deletions packages/reactive/src/datatree.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,62 @@
import { ProxyRaw, RawNode } from './environment'
import { PropertyKey } from './types'
import { ObservablePath, PropertyKey, IOperation } from './types'
import { concat } from './concat'

export class DataChange {
path: ObservablePath
key: PropertyKey
type: string
value: any
oldValue: any
constructor(operation: IOperation, node: DataNode) {
this.key = operation.key
this.type = operation.type
this.value = operation.value
this.oldValue = operation.oldValue
this.path = concat(node.path, operation.key)
}
}
export class DataNode {
target: any

key: PropertyKey

constructor(target: any, key: PropertyKey) {
this.target = target
this.key = key
}

get path() {
if (!this.parent) return this.key ? [this.key] : []
return concat(this.parent.path, this.key)
}

get targetRaw() {
return ProxyRaw.get(this.target) || this.target
}

get parent() {
if (!this.target) return
return RawNode.get(this.targetRaw)
}

isEqual(node: DataNode) {
return node.targetRaw === this.targetRaw && node.key === this.key
}

contains(node: DataNode) {
if (node === this) return true
let parent = node.parent
while (!!parent) {
if (this.isEqual(parent)) return true
parent = parent.parent
}
return false
}
}

export const buildDataTree = (target: any, key: PropertyKey, value: any) => {
const raw = ProxyRaw.get(value) || value
const currentNode = RawNode.get(raw)
const currentNode = RawNode.get(ProxyRaw.get(value) || value)
if (currentNode) return currentNode
const parentRaw = ProxyRaw.get(target) || target
const parentNode = RawNode.get(parentRaw)
if (parentNode) {
RawNode.set(value, {
get path() {
return concat(parentNode.path, key)
},
parent: parentNode,
observers: [],
deepObservers: [],
})
} else {
RawNode.set(value, {
path: [],
observers: [],
deepObservers: [],
})
}
RawNode.set(value, new DataNode(target, key))
}
6 changes: 4 additions & 2 deletions packages/reactive/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { IRawNode, Reaction, ReactionsMap } from './types'
import { ObservableListener, Reaction, ReactionsMap } from './types'
import { DataNode } from './datatree'

export const ProxyRaw = new WeakMap()
export const RawProxy = new WeakMap()
export const RawShallowProxy = new WeakMap()
export const RawNode = new WeakMap<object, IRawNode>()
export const RawNode = new WeakMap<object, DataNode>()
export const RawReactionsMap = new WeakMap<object, ReactionsMap>()

export const ReactionStack: Reaction[] = []
Expand All @@ -13,3 +14,4 @@ export const BatchScope = { value: false }
export const PendingReactions = new Set<Reaction>()
export const PendingScopeReactions = new Set<Reaction>()
export const MakeObservableSymbol = Symbol('MakeObservableSymbol')
export const ObserverListeners = new Set<ObservableListener>()
1 change: 0 additions & 1 deletion packages/reactive/src/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const isAnnotation = (target: any): target is Annotation => {
}

export const isSupportObservable = (target: any) => {
if (isObservable(target)) return false
if (!isValid(target)) return false
if (isArr(target)) return true
if (isPlainObj(target)) {
Expand Down
30 changes: 14 additions & 16 deletions packages/reactive/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
runReactionsFromTargetKey,
} from './reaction'
import { ProxyRaw, RawProxy } from './environment'
import { isSupportObservable } from './externals'
import { isObservable, isSupportObservable } from './externals'
import { createObservable } from './internals'

const wellKnownSymbols = new Set(
Expand All @@ -16,13 +16,13 @@ const hasOwnProperty = Object.prototype.hasOwnProperty

function findObservable(target: any, key: PropertyKey, value: any) {
const observableObj = RawProxy.get(value)
if (isSupportObservable(value)) {
if (observableObj) {
return observableObj
}
if (observableObj) {
return observableObj
}
if (!isObservable(value) && isSupportObservable(value)) {
return createObservable(target, key, value)
}
return observableObj || value
return value
}

function patchIterator(
Expand Down Expand Up @@ -171,10 +171,10 @@ export const baseHandlers: ProxyHandler<any> = {
}
bindTargetKeyWithCurrentReaction({ target, key, receiver, type: 'get' })
const observableResult = RawProxy.get(result)
if (isSupportObservable(result)) {
if (observableResult) {
return observableResult
}
if (observableResult) {
return observableResult
}
if (!isObservable(result) && isSupportObservable(result)) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, key)
if (
!descriptor ||
Expand All @@ -183,7 +183,7 @@ export const baseHandlers: ProxyHandler<any> = {
return createObservable(target, key, result)
}
}
return observableResult || result
return result
},
has(target, key) {
const result = Reflect.has(target, key)
Expand All @@ -198,15 +198,13 @@ export const baseHandlers: ProxyHandler<any> = {
const hadKey = hasOwnProperty.call(target, key)
const newValue = createObservable(target, key, value)
const oldValue = target[key]
const result = Reflect.set(target, key, newValue, receiver)
if (target !== ProxyRaw.get(receiver)) {
return result
}
target[key] = newValue
if (!hadKey) {
runReactionsFromTargetKey({
target,
key,
value: newValue,
oldValue,
receiver,
type: 'add',
})
Expand All @@ -220,7 +218,7 @@ export const baseHandlers: ProxyHandler<any> = {
type: 'set',
})
}
return result
return true
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
Expand Down
Loading

0 comments on commit a1e7200

Please sign in to comment.