Skip to content

Commit e91b0cb

Browse files
authored
[v2] breaking: do not copy initial objects (#815)
* [v2] breaking: do not copy initial objects * fix deepClone * refactor * ah we need it * deep clone * minor fix
1 parent 2c33752 commit e91b0cb

File tree

4 files changed

+59
-64
lines changed

4 files changed

+59
-64
lines changed

src/vanilla.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ const buildProxyFunction = (
127127

128128
versionHolder = [1, 1] as [number, number],
129129

130-
proxyFunction = <T extends object>(initialObject: T): T => {
131-
if (!isObject(initialObject)) {
130+
proxyFunction = <T extends object>(baseObject: T): T => {
131+
if (!isObject(baseObject)) {
132132
throw new Error('object required')
133133
}
134-
const found = proxyCache.get(initialObject) as T | undefined
134+
const found = proxyCache.get(baseObject) as T | undefined
135135
if (found) {
136136
return found
137137
}
@@ -167,18 +167,23 @@ const buildProxyFunction = (
167167
string | symbol,
168168
readonly [ProxyState, RemoveListener?]
169169
>()
170-
const addPropListener = (
171-
prop: string | symbol,
172-
propProxyState: ProxyState
173-
) => {
174-
if (import.meta.env?.MODE !== 'production' && propProxyStates.has(prop)) {
175-
throw new Error('prop listener already exists')
176-
}
177-
if (listeners.size) {
178-
const remove = propProxyState[3](createPropListener(prop))
179-
propProxyStates.set(prop, [propProxyState, remove])
180-
} else {
181-
propProxyStates.set(prop, [propProxyState])
170+
const addPropListener = (prop: string | symbol, propValue: unknown) => {
171+
const propProxyState =
172+
!refSet.has(propValue as object) &&
173+
proxyStateMap.get(propValue as object)
174+
if (propProxyState) {
175+
if (
176+
import.meta.env?.MODE !== 'production' &&
177+
propProxyStates.has(prop)
178+
) {
179+
throw new Error('prop listener already exists')
180+
}
181+
if (listeners.size) {
182+
const remove = propProxyState[3](createPropListener(prop))
183+
propProxyStates.set(prop, [propProxyState, remove])
184+
} else {
185+
propProxyStates.set(prop, [propProxyState])
186+
}
182187
}
183188
}
184189
const removePropListener = (prop: string | symbol) => {
@@ -212,9 +217,7 @@ const buildProxyFunction = (
212217
}
213218
return removeListener
214219
}
215-
const baseObject = Array.isArray(initialObject)
216-
? []
217-
: Object.create(Object.getPrototypeOf(initialObject))
220+
let initializing = true
218221
const handler: ProxyHandler<T> = {
219222
deleteProperty(target: T, prop: string | symbol) {
220223
const prevValue = Reflect.get(target, prop)
@@ -226,7 +229,7 @@ const buildProxyFunction = (
226229
return deleted
227230
},
228231
set(target: T, prop: string | symbol, value: any, receiver: object) {
229-
const hasPrevValue = Reflect.has(target, prop)
232+
const hasPrevValue = !initializing && Reflect.has(target, prop)
230233
const prevValue = Reflect.get(target, prop, receiver)
231234
if (
232235
hasPrevValue &&
@@ -257,40 +260,32 @@ const buildProxyFunction = (
257260
if (!proxyStateMap.has(value) && canProxy(value)) {
258261
nextValue = proxyFunction(value)
259262
}
260-
const childProxyState =
261-
!refSet.has(nextValue) && proxyStateMap.get(nextValue)
262-
if (childProxyState) {
263-
addPropListener(prop, childProxyState)
264-
}
263+
addPropListener(prop, nextValue)
265264
}
266265
Reflect.set(target, prop, nextValue, receiver)
267266
notifyUpdate(['set', [prop], value, prevValue])
268267
return true
269268
},
270269
}
271270
const proxyObject = newProxy(baseObject, handler)
272-
proxyCache.set(initialObject, proxyObject)
271+
proxyCache.set(baseObject, proxyObject)
273272
const proxyState: ProxyState = [
274273
baseObject,
275274
ensureVersion,
276275
createSnapshot,
277276
addListener,
278277
]
279278
proxyStateMap.set(proxyObject, proxyState)
280-
Reflect.ownKeys(initialObject).forEach((key) => {
279+
Reflect.ownKeys(baseObject).forEach((key) => {
281280
const desc = Object.getOwnPropertyDescriptor(
282-
initialObject,
281+
baseObject,
283282
key
284283
) as PropertyDescriptor
285-
if ('value' in desc) {
286-
proxyObject[key as keyof T] = initialObject[key as keyof T]
287-
// We need to delete desc.value because we already set it,
288-
// and delete desc.writable because we want to write it again.
289-
delete desc.value
290-
delete desc.writable
284+
if ('value' in desc && desc.writable) {
285+
proxyObject[key as keyof T] = baseObject[key as keyof T]
291286
}
292-
Object.defineProperty(baseObject, key, desc)
293287
})
288+
initializing = false
294289
return proxyObject
295290
}
296291
) =>
@@ -312,8 +307,8 @@ const buildProxyFunction = (
312307

313308
const [defaultProxyFunction] = buildProxyFunction()
314309

315-
export function proxy<T extends object>(initialObject: T = {} as T): T {
316-
return defaultProxyFunction(initialObject)
310+
export function proxy<T extends object>(baseObject: T = {} as T): T {
311+
return defaultProxyFunction(baseObject)
317312
}
318313

319314
export function getVersion(proxyObject: unknown): number | undefined {

src/vanilla/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { devtools } from './utils/devtools.ts'
44
export { derive, underive, unstable_deriveSubscriptions } from 'derive-valtio'
55
export { addComputed_DEPRECATED as addComputed } from './utils/addComputed.ts'
66
export { proxyWithComputed_DEPRECATED as proxyWithComputed } from './utils/proxyWithComputed.ts'
7+
export { deepClone } from './utils/deepClone.ts'
78
export { proxyWithHistory } from './utils/proxyWithHistory.ts'
89
export { proxySet } from './utils/proxySet.ts'
910
export { proxyMap } from './utils/proxyMap.ts'

src/vanilla/utils/deepClone.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { unstable_buildProxyFunction as buildProxyFunction } from '../../vanilla.ts'
2+
3+
const isObject = (x: unknown): x is object =>
4+
typeof x === 'object' && x !== null
5+
6+
let defaultRefSet: WeakSet<object> | undefined
7+
const getDefaultRefSet = (): WeakSet<object> => {
8+
if (!defaultRefSet) {
9+
defaultRefSet = buildProxyFunction()[2]
10+
}
11+
return defaultRefSet
12+
}
13+
14+
export const deepClone = <T>(obj: T, getRefSet = getDefaultRefSet): T => {
15+
if (!isObject(obj) || getRefSet().has(obj)) {
16+
return obj
17+
}
18+
const baseObject: T = Array.isArray(obj)
19+
? []
20+
: Object.create(Object.getPrototypeOf(obj))
21+
Reflect.ownKeys(obj).forEach((key) => {
22+
baseObject[key as keyof T] = deepClone(obj[key as keyof T], getRefSet)
23+
})
24+
return baseObject
25+
}

src/vanilla/utils/proxyWithHistory.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,10 @@
1-
import {
2-
unstable_buildProxyFunction as buildProxyFunction,
3-
proxy,
4-
ref,
5-
snapshot,
6-
subscribe,
7-
} from '../../vanilla.ts'
1+
import { proxy, ref, snapshot, subscribe } from '../../vanilla.ts'
82
import type { INTERNAL_Snapshot as Snapshot } from '../../vanilla.ts'
3+
import { deepClone } from './deepClone.ts'
94

105
type SnapshotOrUndefined<T> = Snapshot<T> | undefined
116
type Snapshots<T> = Snapshot<T>[]
127

13-
const isObject = (x: unknown): x is object =>
14-
typeof x === 'object' && x !== null
15-
16-
let refSet: WeakSet<object> | undefined
17-
18-
const deepClone = <T>(obj: T): T => {
19-
if (!refSet) {
20-
refSet = buildProxyFunction()[2]
21-
}
22-
if (!isObject(obj) || refSet.has(obj)) {
23-
return obj
24-
}
25-
const baseObject: T = Array.isArray(obj)
26-
? []
27-
: Object.create(Object.getPrototypeOf(obj))
28-
Reflect.ownKeys(obj).forEach((key) => {
29-
baseObject[key as keyof T] = deepClone(obj[key as keyof T])
30-
})
31-
return baseObject
32-
}
33-
348
/**
359
* proxyWithHistory
3610
*

0 commit comments

Comments
 (0)