-
-
Notifications
You must be signed in to change notification settings - Fork 239
How valtio works
Daishi Kato edited this page Jul 18, 2021
·
11 revisions
Ref: https://github.com/pmndrs/valtio/issues/171
This is to describe the high level abstraction of valtio.
import { proxy, subscribe } from 'valtio'
const s1 = proxy({})
subscribe(s1, () => { console.log('s1 is changed!') })
s1.a = 1 // s1 is changed!
++s1.a // s1 is changed!
delete s1.a // s1 is changed!
s1.b = 2 // s1 is changed!
s1.b = 2 // (not changed)
s1.obj = {} // s1 is changed!
s1.obj.c = 3 // s1 is changed!
const s2 = s1.obj
subscribe(s2 () => { console.log('s2 is changed!') })
s1.obj.d = 4 // s1 is changed! and s2 is changed!
s2.d = 5 // s1 is changed! and s2 is changed!
const s3 = proxy({})
subscribe(s3 () => { console.log('s3 is changed!') })
s1.o = s3
s3.p = 'hello' // s1 is changed! and s3 is changed!
s2.q = s3
s3.p = 'hi' // s1 is changed! s2 is changed! and s3 is changed!
s1.x = s1
s1.a += 1 // s1 is changed!
valtio has two kinds of proxies, for write and read. We intentionally separate them for hooks and concurrent react.
proxy()
creates a proxy object to detect mutation, "proxy for write"
snapshot()
creates an immutable object from the proxy object
useSnapshot()
wraps the snapshot object again with another proxy (with proxy-compare
) to detect property access, "proxy for read"
const state = proxy({ a: { aa: 1 }, b: { bb: 2 } })
const snap1 = snapshot(state)
console.log(snap1) // ---> { a: { aa: 1 }, b: { bb: 2 } }
++state.a.aa
const snap2 = snapshot(state)
console.log(snap2) // ---> { a: { aa: 2 }, b: { bb: 2 } }
snap1.b === snap2.b // this is `true`, it doesn't create a new snapshot because no properties are changed.
valtio's proxy has only one goal: create an immutable snapshot object
some design principles:
- snapshot is created on demand
- changes are tracked only with version number
- subscription is used for notifying update (version)
- version number is hidden as implementation detail
- proxies are basically used only for version and subscription
- snapshot creation is optimized with version number
some notes about the implementation:
- proxy can be nested (created at the initialization)
- proxy can have circular structure (globalVersion to detect it)
some notes about promise handling:
- proxy can have a promise but does nothing
- when creating a snapshot, it will store the resolved value
- if it's not resolved, a special object will throw a promise/error
Suggestion notes:
- Starting with
useSnapshot()
what proxies are created and what are their conceptual names? Briefly, how isuseMutableSource()
source used to setup the subscription? It looks like there are listeners on a proxy, for deep property changes, how are those stored and tracked? What will trigger the subscription to be fired? Be detailed here. - Walk through the flow of a property change for something non-trivial like
address.person.name = "Bob"
Step-by-step, from the moment of the assignment, what happens? How is the change notification queued? Is it through a React hook? When the change notification actually occurs, how is a re-render triggered? Leave no stone unturned as this is a very complicated interaction.