path_based_state.md (2279B)
1 # Path-Based State (Deep State) 2 3 ``` 4 State Tree 5 +----------------------------------+ 6 | app | 7 | user | 8 | profile | 9 | name: "Alice" <--- path | 10 | age: 30 | 11 | cart | 12 | count: 2 | 13 +----------------------------------+ 14 15 subscribe("user.profile.name") --> only fires when name changes 16 subscribe("cart.count") --> only fires when count changes 17 ``` 18 19 Instead of subscribing to the whole state object, subscribers listen to a 20 specific path within a nested state tree — e.g. "user.profile.name". Only 21 changes to that exact path trigger their callback. 22 23 ```ts 24 type Path<T> = string; // e.g. "user.profile.name" 25 26 class DeepStore<T extends object> { 27 private state: T; 28 private subscribers = new Map<string, Set<(val: unknown) => void>>(); 29 30 constructor(initial: T) { 31 this.state = initial; 32 } 33 34 subscribe<V>(path: Path<T>, cb: (val: V) => void): () => void { 35 if (!this.subscribers.has(path)) this.subscribers.set(path, new Set()); 36 this.subscribers.get(path)!.add(cb as (val: unknown) => void); 37 return () => 38 this.subscribers.get(path)?.delete(cb as (val: unknown) => void); 39 } 40 41 set(path: Path<T>, value: unknown) { 42 const keys = path.split("."); 43 let cursor: any = this.state; 44 for (let i = 0; i < keys.length - 1; i++) cursor = cursor[keys[i]]; 45 cursor[keys[keys.length - 1]] = value; 46 this.subscribers.get(path)?.forEach((cb) => cb(value)); 47 } 48 49 get(path: Path<T>): unknown { 50 return path.split(".").reduce((obj: any, key) => obj?.[key], this.state); 51 } 52 } 53 54 // Usage 55 interface AppState { 56 user: { profile: { name: string; age: number } }; 57 cart: { count: number }; 58 } 59 60 const store = new DeepStore<AppState>({ 61 user: { profile: { name: "Alice", age: 30 } }, 62 cart: { count: 2 }, 63 }); 64 65 store.subscribe<string>( 66 "user.profile.name", 67 (name) => console.log(`Name changed: ${name}`), 68 ); 69 store.set("user.profile.name", "Bob"); // fires callback 70 store.set("cart.count", 5); // does NOT fire name callback 71 ``` 72 73 Use case: Large nested state trees where you want granular, performant 74 subscriptions — only the parts of the UI that care about a specific slice 75 re-render.