mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
feat(packages/vue): add useAppSharedState composable
This commit is contained in:
40
packages/vue/src/composables/useAppSharedState/index.test.ts
Normal file
40
packages/vue/src/composables/useAppSharedState/index.test.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, it, vi, expect } from 'vitest';
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useAppSharedState } from '.';
|
||||||
|
|
||||||
|
describe('useAppSharedState', () => {
|
||||||
|
it('should initialize state only once', () => {
|
||||||
|
const stateFactory = (initValue?: number) => {
|
||||||
|
const count = ref(initValue ?? 0);
|
||||||
|
return { count };
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSharedState = useAppSharedState(stateFactory);
|
||||||
|
|
||||||
|
const state1 = useSharedState(1);
|
||||||
|
const state2 = useSharedState(2);
|
||||||
|
|
||||||
|
expect(state1.count.value).toBe(1);
|
||||||
|
expect(state2.count.value).toBe(1);
|
||||||
|
expect(state1).toBe(state2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same state object across different calls', () => {
|
||||||
|
const stateFactory = () => {
|
||||||
|
const state = reactive({ count: 0 });
|
||||||
|
const increment = () => state.count++;
|
||||||
|
return { state, increment };
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSharedState = useAppSharedState(stateFactory);
|
||||||
|
|
||||||
|
const sharedState1 = useSharedState();
|
||||||
|
const sharedState2 = useSharedState();
|
||||||
|
|
||||||
|
expect(sharedState1.state.count).toBe(0);
|
||||||
|
sharedState1.increment();
|
||||||
|
expect(sharedState1.state.count).toBe(1);
|
||||||
|
expect(sharedState2.state.count).toBe(1);
|
||||||
|
expect(sharedState1).toBe(sharedState2);
|
||||||
|
});
|
||||||
|
});
|
||||||
40
packages/vue/src/composables/useAppSharedState/index.ts
Normal file
40
packages/vue/src/composables/useAppSharedState/index.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { AnyFunction } from '@robonen/stdlib';
|
||||||
|
import { effectScope, onScopeDispose } from 'vue';
|
||||||
|
|
||||||
|
// TODO: maybe we should control subscriptions and dispose them when the child scope is disposed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name useAppSharedState
|
||||||
|
* @category State
|
||||||
|
* @description Provides a shared state object for use across Vue instances
|
||||||
|
*
|
||||||
|
* @param {Function} stateFactory The factory function to create the shared state
|
||||||
|
* @returns {Function} The shared state object
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const useSharedState = useAppSharedState((initValue?: number) => {
|
||||||
|
* const count = ref(initValue ?? 0);
|
||||||
|
* return { count };
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const useSharedState = useAppSharedState(() => {
|
||||||
|
* const state = reactive({ count: 0 });
|
||||||
|
* const increment = () => state.count++;
|
||||||
|
* return { state, increment };
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useAppSharedState<Fn extends AnyFunction>(stateFactory: Fn) {
|
||||||
|
let initialized = false;
|
||||||
|
let state: ReturnType<Fn>;
|
||||||
|
const scope = effectScope(true);
|
||||||
|
|
||||||
|
return ((...args: Parameters<Fn>) => {
|
||||||
|
if (!initialized) {
|
||||||
|
state = scope.run(() => stateFactory(...args));
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user