Vue
Use the Vue adapter inside setup() or an active effectScope. Import from @ilokesto/state/vue.
Plain state composable
Plain state returns { state: ComputedRef<S>, setState }.
import { create } from '@ilokesto/state/vue';
const useCounter = create({ count: 0, label: 'Clicks' });
export default {
setup() {
const { state: count, setState } = useCounter((state) => state.count);
const { state: label } = useCounter((state) => state.label);
const increment = () => setState((prev) => ({ ...prev, count: prev.count + 1 }));
return { count, label, increment };
},
};state is a ComputedRef, so templates unwrap it naturally while script code can read state.value.
Reducer composable
import { create } from '@ilokesto/state/vue';
type Action = { type: 'increment' } | { type: 'reset' };
const reduce = (state: { count: number }, action: Action) => {
if (action.type === 'increment') return { count: state.count + 1 };
if (action.type === 'reset') return { count: 0 };
return state;
};
const useCounter = create(reduce, { count: 0 });
export function useCounterActions() {
const { state, dispatch } = useCounter((state) => state.count);
const increment = () => dispatch({ type: 'increment' });
return { count: state, increment };
}Outside Vue scope
Vue reactive calls intentionally throw outside setup()/effectScope. Use sync helpers for module code.
const count = useCounter.readOnly((state) => state.count);
const dispatch = useCounter.writeOnly();
dispatch({ type: 'reset' });Caveats
- Reactive reads require
getCurrentScope()to exist. - Cleanup is registered with
onScopeDispose, so do not bypass Vue scope for subscribed reads. - Use
readOnly()for one-time reads; it is not reactive.