Global Watch
The store.watch
method is used to globally monitor data changes in State
. When the monitored data changes, a listener function can be executed.
Watch Method
The signature of the watch
method is as follows:
// Watch all
watch(listener:WatchListener,options?:WatchListenerOptions):Watcher
// Watch specified paths only
watch(paths:'*' | string | (string|string[])[],
listener:WatchListener,options?:WatchListenerOptions
):Watcher
Returns Watcher
type, used to cancel the watch.
type Watcher = { off:()=>void }
Watch Listener
WatchListener
is a function used to handle monitored data changes, with the following signature:
type WatchListener<Value=any,Parent=any> =
(operate:StateOperate<Value,Parent>)=>void
type StateOperate<Value=any,Parent=any> = {
type : StateOperateType,
path : string[],
value : Value,
indexs? : number[],
oldValue? : Value,
parentPath?: string[],
parent? : Parent,
reply? : boolean
}
- When data changes are detected,
watch
will call theWatchListener
function and pass aStateOperate
object. - The
StateOperate
object includes information such as the change typetype
,path
,value
, etc.
The properties of the StateOperate
object are as follows:
Property | Type | Description |
---|---|---|
type | string | State operation type, values: get ,set ,delete ,insert ,update ,remove ,batch |
path | string[] | State path |
value | any | Value |
indexs | number[] | Array operation indices |
oldValue | any | Old value |
parentPath | string[] | Parent path |
parent | any | Parent value |
reply | boolean | Whether it's a replay during batch operations |
watch
can monitor state read and write operations, includingget
,set
,delete
,insert
,update
,remove
,batch
operations.get
,set
,delete
are suitable for object value read/writeinsert
,update
,remove
are suitable for array operationsbatch
is suitable for batch operations, triggered when usingbatchUpdate
, see Batch Operations- The
reply
parameter indicates whether the operation is an event replay during batch updates.
Note
The listener function must be synchronous
Watch Options
type WatchListenerOptions = {
once? : boolean
operates?: '*' | 'read' | 'write' | StateOperateType[] // Operation types to watch
filter? : (args:StateOperate)=>boolean // Filter
}
Property | Type | Description |
---|---|---|
once | boolean | Whether to watch only once |
operates | '*'| 'read' | 'write' | StateOperateType[] | Operation types to watch |
filter | (args:StateOperate)=>boolean | Filter function, executes listener if returns true |
- The most important parameter of the watch function is
operates
, used to configure which operation types to watch. Can be'*'
,'read'
,'write'
, or an array of operation types. - Default
operates='write'
, watches all write operations. operates='get'
watches all read operations.operates='*'
watches all read/write/delete operations.operates
can also be an array of operation types, like['set','delete']
, watchingset
anddelete
operations.- The
once
property configures whether to watch only once. - The
filter
function filters watched operations, executes listener if returnstrue
.
Example:
store.watch((operate)=>{
....
},{
operates:'write'
})
Global Watching
Use watch(listener,options?)
method to globally watch data changes in State
. The listener function will execute for any state operation.
import React from "react"
import { createStore, computed,useStore } from "@autostorejs/react"
import { Box,Button,ColorBlock,Layout,CheckBox } from "x-react-components"
import { useEffect,useRef } from "react"
const { state,watch,$ } = createStore({
order:{
price:10,
count:2,
total:computed((order)=>{
return order.price*order.count
})
}
})
export default ()=>{
const ref = useRef<HTMLDivElement>(null)
const op = useStore({
operates:'*'
})
const [ops,setOps] = op.useState('operates')
useEffect(()=>{
const watcher = watch((operate)=>{
ref.current!.insertAdjacentHTML("beforeend",`<p style='margin:2px;'}>${operate.type} ${operate.path.join('.')}</p>`)
},{
operates:ops as any
})
return ()=>watcher.off()
},[ops])
return (<Layout style={{maxHeight:'400px'}}>
<div>
<ColorBlock name="Price">{$('order.price')}</ColorBlock>
<ColorBlock name="Count">
<Button onClick={()=>{
state.order.count--
ref.current!.insertAdjacentHTML("beforeend",`----------`)
}}>-</Button>
{$('order.count')}
<Button onClick={()=>{
state.order.count++
ref.current!.insertAdjacentHTML("beforeend",`----------`)
}}>+</Button>
</ColorBlock>
<ColorBlock name="Total">{$('order.total')}</ColorBlock>
<Box>
<CheckBox id="watch-all" label="监听所有操作" checked={ops==='*'} onChange={(e)=>{
setOps(e.target.checked ? '*' : 'read')
}}/>
<CheckBox id="watch-write" label="只监听写操作" checked={ops==='write'} onChange={(e)=>{
setOps(e.target.checked ? 'write' : '*')
}}/>
<CheckBox id="watch-read" label="只监听读操作" checked={ops==='read'} onChange={(e)=>{
setOps(e.target.checked ? 'read' : '*')
}}/>
</Box>
<Button onClick={()=>{
ref.current!.innerHTML = ''
}}>Clear</Button>
</div>
<div ref={ref} style={{
overflowY:'auto',
}}>
</div>
</Layout>)
}
- Monitor all data changes through the
watch
method. When data changes, the listener function will execute. watch.options
supports specifying which operation types to watch, likewatch(listener,{operates:['set','delete']})
.
Local Watching
Besides global watching, you can also use watch(paths,listener,options?)
method to watch state data changes only at specified paths.
import React from "react"
import { createStore, computed,useStore } from "@autostorejs/react"
import { Box,Button,ColorBlock,Layout,CheckBox } from "x-react-components"
import { useEffect,useRef } from "react"
const { state,watch,$ } = createStore({
order:{
price:10,
count:2,
total:computed((order)=>{
return order.price*order.count
})
}
})
export default ()=>{
const ref = useRef<HTMLDivElement>(null)
const op = useStore({
operates:'*'
})
const [ops,setOps] = op.useState('operates')
useEffect(()=>{
const watcher = watch("order.total",(operate)=>{
ref.current!.insertAdjacentHTML("beforeend",`<p style='margin:2px;'}>${operate.type} ${operate.path.join('.')}</p>`)
},{
operates:ops as any
})
return ()=>watcher.off()
},[ops])
return (<Layout style={{maxHeight:'400px'}}>
<div>
<ColorBlock name="Price">{$('order.price')}</ColorBlock>
<ColorBlock name="Count">
<Button onClick={()=>{
state.order.count--
ref.current!.insertAdjacentHTML("beforeend",`----------`)
}}>-</Button>
{$('order.count')}
<Button onClick={()=>{
state.order.count++
ref.current!.insertAdjacentHTML("beforeend",`----------`)
}}>+</Button>
</ColorBlock>
<ColorBlock name="Total">{$('order.total')}</ColorBlock>
<Box>
<CheckBox id="watch-all" label="监听所有操作" checked={ops==='*'} onChange={(e)=>{
setOps(e.target.checked ? '*' : 'read')
}}/>
<CheckBox id="watch-write" label="只监听写操作" checked={ops==='write'} onChange={(e)=>{
setOps(e.target.checked ? 'write' : '*')
}}/>
<CheckBox id="watch-read" label="只监听读操作" checked={ops==='read'} onChange={(e)=>{
setOps(e.target.checked ? 'read' : '*')
}}/>
</Box>
<Button onClick={()=>{
ref.current!.innerHTML = ''
}}>Clear</Button>
</div>
<div ref={ref} style={{
overflowY:'auto',
}}>
</div>
</Layout>)
}
- You can watch multiple paths at once, like
watch(['order.price','order.count'],listener)
. - You can even watch paths containing wildcards, like
watch('order.*'],listener)
.
Array Watching
watch
also supports array watching, like watch('order.books',listener)
. When the order.books
array changes, the listener function will execute.
The difference from normal watching is in the events #️⃣
- Array watch events have three types:
insert
,update
,remove
. - Array member operation parameters have an additional
indexs
property to mark array indices. - The
get
operation event also applies to arrays
import React from "react"
import { createStore } from "@autostorejs/react"
import { Button,Layout,Input} from "x-react-components"
import { useEffect,useRef } from "react"
const { state,watch,useFields } = createStore({
order:{
price:10,
count:2,
books:[
"AutoStore实战指南",
"深入浅出AutoStore",
"AutoStore最佳实践"
]
}
})
export default ()=>{
const ref = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
useEffect(()=>{
const watcher = watch("order.books",(operate)=>{
ref.current!.insertAdjacentHTML("beforeend",`<p style='margin:2px;'}>
${operate.type} ${operate.path.join('.')}[${operate.indexs![0]}]
</p>`)
},{
operates:['insert','remove','update']
})
return ()=>watcher.off()
},[])
const bindBooks = useFields().order.books
return (<Layout style={{maxHeight:'400px'}}>
<div>
{
state.order.books.map((book,index)=>{
return <Input key={index} {...bindBooks[index]} actions={["X"]}
onAction={()=>{
state.order.books.splice(index,1)
}}
/>
})
}
<Input ref={inputRef} actions={["+"]}
placeholder="请输入书名"
onAction={(id,val)=>{
if(String(val).length>0){
state.order.books.push(val)
inputRef.current!.value=''
}
}}/>
<Button onClick={()=>{
ref.current!.innerHTML = ''
}}>Clear</Button>
</div>
<div ref={ref} style={{
overflowY:'auto',
}}>
</div>
</Layout>)
}
Dependency Collection
AutoStore
's dependency collection functionality is implemented based on the watch
feature.
Here's the code for dependency collection during synchronous computed property initialization:
function collectDependencies(){
let dependencies:string[][] = []
// 1. Watch all get operations
const watcher = this.store.watch((event)=>{
// Save dependency paths
dependencies.push(event.path)
},{operates:['get']})
// 2. Run the synchronous computed getter function once
this.run({first:true})
// 3. End watching after dependency collection is complete
watcher.off()
// .......
return dependencies
}
INFO
The store.watch
method is used to globally monitor data changes in State
, and computed properties are also implemented based on the watch
method.