How valtio works
Ref: https://github.com/pmndrs/valtio/issues/171
This is to describe the high level abstraction of valtio.
Articles
Examples
proxy() by examples
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!
snapshot() by examples
import { proxy, snapshot } from 'valtio'
const p = proxy({})
const s1 = snapshot(p) // is {} but not wrapped by a proxy
const s2 = snapshot(p)
s1 === s2 // is true because p wasn't changed
p.a = 1 // mutate the proxy
const s3 = snapshot(p) // is { a: 1 }
p.a = 1 // mutation bails out and proxy is not updated
const s4 = snapshot(p)
s3 === s4 // is still true
p.a = 2 // mutate it
const s5 = snapshot(p) // is { a: 2 }
p.a = 1 // mutate it back
const s6 = snapshot(p) // creates a new snapshot
s3 !== s6 // is true (different snapshots, even though they are deep equal)
p.obj = { b: 2 } // attaching a new object, which will be wrapped by a proxy
const s7 = snapshot(p) // is { a: 1, obj: { b: 2 } }
p.a = 2 // mutating p
const s8 = snapshot(p) // is { a: 2, obj: { b: 2 } }
s7 !== s8 // is true because a is different
s7.obj === s8.obj // is true because obj is not changed
useSnapshot() by examples
import { proxy, useSnapshot } from 'valtio'
const s1 = proxy({
  counter: 0,
  text: 'Good morning from valtio',
  foo: {
    boo: 'baz'
  }
})
const MyComponent = () => {
  // Using destructuring
  const { text, counter } = useSnapshot(state)
  // Multilevel destructiong works as well
  const { text, counter, { foo }} = useSnapshot(state)
  // Assigning to a snapshot obeject
  const snap = useSnapshot(state)
  return (() => {
    <div id="main">
      <h1>{ `${foo} - ${text}` }</h1>
      {/* - or - */}
      <h1>{ `${snap.foo.bar} = `${snap.text}}</h1>
      <div>
        <input
          type="input"
          {/* we use snapshot for reading */}
          value={text}
          {/* the line above equivalent to this */}
          value={snap.text}
          {/* we use the proxy (s1) for mutations */}
          onChange={e => {
            s1.text = e.target.value
          }}
        />
      </div>
      <div>
        { counter }
        <button onClick={() => s1.counter++}> + </button>
        <button onClick={() => s1.counter--}> - </button>
      </di>
    </div>
  })
}
Unorganized Notes
two kinds of proxies
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"
snapshot creation is optimized
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.
Some notes about valtio implementation in deep
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