From 57a8cb828f3487c5d4f3731f5c4c3d4ca083d89d Mon Sep 17 00:00:00 2001 From: Dmitriy Rudnyk Date: Mon, 26 Feb 2024 00:05:49 +0200 Subject: [PATCH] refact useTrackedInstance types --- src/collection.ts | 14 ++--- src/tracked-instance.ts | 123 +++++----------------------------------- src/utils.ts | 102 +++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 117 deletions(-) create mode 100644 src/utils.ts diff --git a/src/collection.ts b/src/collection.ts index fc7f350..d320a7d 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,24 +1,24 @@ -import {computed, shallowRef, triggerRef, ShallowRef, ComputedRef, ref, Ref} from 'vue' +import {computed, ComputedRef, ref, Ref, ShallowRef, shallowRef, triggerRef} from 'vue' import {TrackedInstance, useTrackedInstance} from './tracked-instance' -export interface CollectionItem, Meta = Record> { +export interface CollectionItem { instance: TrackedInstance meta: Meta isRemoved: Ref isNew: Ref } -export interface Collection, Meta = Record> { +export interface Collection { items: ShallowRef[]> isDirty: ComputedRef - add: (item: Partial, afterIndex?: number) => CollectionItem + add: (item: Item, afterIndex?: number) => CollectionItem remove: (index: number, isHardRemove?: boolean) => void loadData: (items: Item[]) => void reset: () => void } -export const useCollection = , Meta = Record>( - createItemMeta: (instance: TrackedInstance) => Meta = () => ({}) as Meta +export const useCollection = ( + createItemMeta: (instance: TrackedInstance) => Meta = () => undefined as Meta ): Collection => { const items = shallowRef[]>([]) @@ -26,7 +26,7 @@ export const useCollection = , Meta = Record instance.isDirty.value || isNew.value || isRemoved.value) ) - const add = (item: Partial, index: number = items.value.length) => { + const add = (item: Item, index: number = items.value.length) => { const instance = useTrackedInstance(item) const newItem = { isRemoved: ref(false), diff --git a/src/tracked-instance.ts b/src/tracked-instance.ts index a70591c..6097110 100644 --- a/src/tracked-instance.ts +++ b/src/tracked-instance.ts @@ -1,115 +1,15 @@ import {get, has, set, unset} from 'lodash-es' -import {computed, customRef, Ref} from 'vue' +import {computed, Ref} from 'vue' +import {createNestedRef, DeepPartial, isEmpty, isObject, iterateObject, NestedProxyPathItem} from './utils' -type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T - -export interface TrackedInstance> { +export interface TrackedInstance { data: Ref isDirty: Ref changedData: Ref> - loadData: (newData: DeepPartial) => void + loadData: (newData: Data) => void reset: () => void } -interface NestedProxyPathItem { - target: Record - property: string - receiver?: Record -} - -const isObject = (value: unknown) => - typeof value === 'object' && - value !== null && - !Array.isArray(value) && - !(value instanceof Date) && - !(value instanceof File) && - !(value instanceof Map) && - !(value instanceof Set) - -const isEmpty = (value: object) => Object.keys(value).length === 0 - -const iterateObject = function* ( - source: Record, - params: { - // define condition when need to go deep - goDeepCondition?: (path: string[], value: any) => boolean - // include parent into separate step when we go deep - includeParent?: boolean - } = {} -) { - const {goDeepCondition = (_, value) => isObject(value), includeParent = false} = params - const iterateObjectDeep = function* (path: string[], obj: Record): Generator<[string[], any]> { - for (const [key, value] of Object.entries(obj)) { - const currentPath = path.concat(key) - if (goDeepCondition(currentPath, value)) { - if (includeParent) { - yield [currentPath, value] - } - yield* iterateObjectDeep(currentPath, value) - } else { - yield [currentPath, value] - } - } - } - - yield* iterateObjectDeep([], source) -} - -const createNestedRef = >( - source: Source, - handler: (path: NestedProxyPathItem[]) => ProxyHandler -) => - customRef((track, trigger) => { - // make nested objects and arrays is reactive - const createProxy = >( - source: InnerSource, - path: NestedProxyPathItem[] = [] - ): InnerSource => { - const currentProxyHandler = handler(path) as unknown as ProxyHandler - return new Proxy(source, { - ...currentProxyHandler, - get(target, property: string, receiver) { - track() - const result = currentProxyHandler.get - ? currentProxyHandler.get(target, property, receiver) - : Reflect.get(target, property, receiver) - - if (isObject(result) || Array.isArray(result)) { - return createProxy(result, path.concat({target, property, receiver})) - } - return result - }, - set(target, property, value, receiver) { - const result = currentProxyHandler.set - ? currentProxyHandler.set(target, property, value, receiver) - : Reflect.set(target, property, value, receiver) - trigger() - return result - }, - deleteProperty(target, property) { - const result = currentProxyHandler.deleteProperty - ? currentProxyHandler.deleteProperty(target, property) - : Reflect.deleteProperty(target, property) - trigger() - return result - } - } as ProxyHandler) - } - - let value = createProxy(source) - - return { - get() { - track() - return value - }, - set(newValue: Source) { - value = createProxy(newValue) - trigger() - } - } - }) - // array values in originalData should store in default object to avoid removing items on change length class ArrayInOriginalData { length: number @@ -207,9 +107,12 @@ const snapshotValueToOriginalData = ( } } -export const useTrackedInstance = >( - initialData: Partial -): TrackedInstance => { +export function useTrackedInstance(): TrackedInstance +export function useTrackedInstance(value: Data): TrackedInstance + +export function useTrackedInstance( + initialData?: Data +): TrackedInstance { type InternalData = { root: Data } const _originalData = createNestedRef>({}, (path) => ({ deleteProperty(target, property) { @@ -236,7 +139,7 @@ export const useTrackedInstance = >( path.map((i) => i.property) ) as ArrayInOriginalData | undefined - const {length: originalDataLength} = originalDataValue || oldValue + const {length: originalDataLength} = originalDataValue || oldValue as any[] if (value < originalDataLength) { // when removed new value @@ -261,7 +164,7 @@ export const useTrackedInstance = >( return Reflect.set(target, property, value, receiver) }, - deleteProperty(target, property: keyof typeof target) { + deleteProperty(target, property) { setOriginalDataValue(_originalData.value, parentThree.concat({target, property} as NestedProxyPathItem)) return Reflect.deleteProperty(target, property) } @@ -299,7 +202,7 @@ export const useTrackedInstance = >( const changedData = computed(() => _changedData.value.root as DeepPartial) - const loadData = (newData: DeepPartial) => { + const loadData = (newData: Data) => { _data.value = {root: newData} as InternalData _originalData.value = {} } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..41ca2c3 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,102 @@ +import {customRef} from 'vue' + +export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T + +export interface NestedProxyPathItem { + target: Record + property: string + receiver?: Record +} + +export const isObject = (value: unknown) => + typeof value === 'object' && + value !== null && + !Array.isArray(value) && + !(value instanceof Date) && + !(value instanceof File) && + !(value instanceof Map) && + !(value instanceof Set) + +export const isEmpty = (value: object) => Object.keys(value).length === 0 + +export const iterateObject = function* ( + source: Record, + params: { + // define condition when need to go deep + goDeepCondition?: (path: string[], value: any) => boolean + // include parent into separate step when we go deep + includeParent?: boolean + } = {} +) { + const {goDeepCondition = (_, value) => isObject(value), includeParent = false} = params + const iterateObjectDeep = function* (path: string[], obj: Record): Generator<[string[], any]> { + for (const [key, value] of Object.entries(obj)) { + const currentPath = path.concat(key) + if (goDeepCondition(currentPath, value)) { + if (includeParent) { + yield [currentPath, value] + } + yield* iterateObjectDeep(currentPath, value) + } else { + yield [currentPath, value] + } + } + } + + yield* iterateObjectDeep([], source) +} + +export const createNestedRef = >( + source: Source, + handler: >(path: NestedProxyPathItem[]) => ProxyHandler +) => + customRef((track, trigger) => { + // make nested objects and arrays is reactive + const createProxy = >( + source: InnerSource, + path: NestedProxyPathItem[] = [] + ): InnerSource => { + const currentProxyHandler = handler(path) as unknown as ProxyHandler + return new Proxy(source, { + ...currentProxyHandler, + get(target, property: string, receiver) { + track() + const result = currentProxyHandler.get + ? currentProxyHandler.get(target, property, receiver) + : Reflect.get(target, property, receiver) + + if (isObject(result) || Array.isArray(result)) { + return createProxy(result, path.concat({target, property, receiver})) + } + return result + }, + set(target, property, value, receiver) { + const result = currentProxyHandler.set + ? currentProxyHandler.set(target, property, value, receiver) + : Reflect.set(target, property, value, receiver) + trigger() + return result + }, + deleteProperty(target, property) { + const result = currentProxyHandler.deleteProperty + ? currentProxyHandler.deleteProperty(target, property) + : Reflect.deleteProperty(target, property) + trigger() + return result + } + } as ProxyHandler) + } + + let value = createProxy(source) + + return { + get() { + track() + return value + }, + set(newValue: Source) { + value = createProxy(newValue) + trigger() + } + } + })