Skip to content

Commit

Permalink
feat(mapwatcher): support watch hook states and add mapWatcher helper
Browse files Browse the repository at this point in the history
  • Loading branch information
JOU-amjs committed Nov 21, 2023
1 parent 3616867 commit 47c22a7
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export const splitStatesAndFn = (useHookReturns: Record<string, any>) => {
* @returns 判断结果
*/
isPlainObject = (arg: any): arg is Record<string, any> => Object.prototype.toString.call(arg) === '[object Object]',
/**
* 判断参数是否为函数
* @param fn 任意参数
* @returns 该参数是否为函数
*/
isFn = (arg: any): arg is (...args: any) => any => typeof arg === 'function',
/**
* 自定义断言函数,表达式为false时抛出错误
* @param expression 判断表达式,true或false
Expand Down
5 changes: 3 additions & 2 deletions src/mapAlovaHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Vue from 'vue';
import { UseHookCallers, UseHookMapGetter, VueHookMapperMixin } from '../typings';
import { isPlainObject, myAssert, splitStatesAndFn } from './helper';

const vueComponentAlovaHookStateKey = 'ALOVA_USE_HOOK_STATES$__';
const vueComponentAlovaHookStateKey = 'alovaHook$';
/**
* 将useHook的返回值和操作函数动态映射到vueComponent上
*
Expand Down Expand Up @@ -40,7 +40,8 @@ export default <GR extends UseHookCallers>(mapGetter: UseHookMapGetter<GR>) => {

// 不设置set函数,禁止覆盖use hook对应的对象
Object.defineProperty(vm, dataKey, {
get: () => (vm as any)[vueComponentAlovaHookStateKey][dataKey] || {}
get: () => (vm as any)[vueComponentAlovaHookStateKey][dataKey] || {},
set: value => ((vm as any)[vueComponentAlovaHookStateKey][dataKey] = value)
});
const [states, fns] = splitStatesAndFn(useHookReturns);
if (vm.$set) {
Expand Down
26 changes: 26 additions & 0 deletions src/mapWatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { WatchOptionsWithHandler } from 'vue';
import { AlovaWatcherHandlers, VueWatchHandler } from '../typings';
import { isFn } from './helper';

const mapWatcher = (watcherHandlers: AlovaWatcherHandlers, withPrefix = true, parentKey = '') => {
const handlerKeys = Object.keys(watcherHandlers || {});
let finalHandlers = {} as Record<string, VueWatchHandler>;
handlerKeys.forEach(key => {
const handlerNames = key.split(/\s*,\s*/);
const handlerOrHandlers = watcherHandlers[key];
handlerNames.forEach(handlerName => {
if (isFn(handlerOrHandlers) || isFn((handlerOrHandlers as WatchOptionsWithHandler<any>).handler)) {
const prefix = withPrefix ? 'alovaHook$.' : '';
finalHandlers[prefix + parentKey + handlerName] = handlerOrHandlers as VueWatchHandler;
} else {
finalHandlers = {
...finalHandlers,
...mapWatcher(handlerOrHandlers as AlovaWatcherHandlers, withPrefix, handlerName + '.')
};
}
});
});
return finalHandlers;
};

export default mapWatcher;
32 changes: 31 additions & 1 deletion test/components/TestRequest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
send
</button>
<span role="extraData">{{ testRequest.extraData }}</span>
<span role="loadingComputed">{{ testRequest$loading ? 'loading' : 'loaded' }}</span>
<div role="dataComputed">{{ JSON.stringify(testRequest$data) }}</div>
<button
role="btnModify"
@click="handleTestRequest$dataModify">
modify data
</button>
</div>
</template>

Expand Down Expand Up @@ -38,7 +45,20 @@ export default {
})
};
}),
emits: ['success', 'error', 'complete'],
emits: ['success', 'error', 'complete', 'watchState'],
computed: {
testRequest$loading() {
return this.testRequest.loading;
},
testRequest$data: {
get() {
return this.testRequest.data;
},
set(v) {
this.testRequest.data = v;
}
}
},
created() {
this.testRequest$onSuccess(event => {
this.$emit('success', event);
Expand All @@ -51,6 +71,16 @@ export default {
this.testRequest$onComplete(event => {
this.$emit('complete', event);
});
},
watch: {
'alovaHook$.testRequest.data'(newVal) {
this.$emit('watchState', newVal);
}
},
methods: {
handleTestRequest$dataModify() {
this.testRequest$data = { modify: true };
}
}
};
</script>
135 changes: 135 additions & 0 deletions test/mapAlovaMutation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import mapWatcher from '../src/mapWatcher';

describe('mapWatcher', () => {
test('single watch with handler', () => {
const fn1 = () => {},
fn2 = () => {};
let watchers = mapWatcher({
testRequest: {
loading: fn1,
data: fn2
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': fn1,
'alovaHook$.testRequest.data': fn2
});

watchers = mapWatcher({
'testRequest.loading': fn1,
'testRequest.data': fn2
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': fn1,
'alovaHook$.testRequest.data': fn2
});
});

test('single watch with detail object', () => {
const obj1 = {
handler() {},
deep: true
},
obj2 = {
handler() {}
};
let watchers = mapWatcher({
testRequest: {
loading: obj1,
data: obj2
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': obj1,
'alovaHook$.testRequest.data': obj2
});

watchers = mapWatcher({
'testRequest.loading': obj1,
'testRequest.data': obj2
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': obj1,
'alovaHook$.testRequest.data': obj2
});
});

test('batch watch', () => {
const fn1 = () => {},
fn2 = () => {};
let watchers = mapWatcher({
'testRequest, testRequest2': {
loading: fn1,
data: fn2
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': fn1,
'alovaHook$.testRequest.data': fn2,
'alovaHook$.testRequest2.loading': fn1,
'alovaHook$.testRequest2.data': fn2
});

watchers = mapWatcher({
'testRequest.loading, testRequest.data': fn1
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': fn1,
'alovaHook$.testRequest.data': fn1
});

watchers = mapWatcher({
'testRequest, testRequest2': {
'loading, data': fn1
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': fn1,
'alovaHook$.testRequest.data': fn1,
'alovaHook$.testRequest2.loading': fn1,
'alovaHook$.testRequest2.data': fn1
});
});

test('batch watch with detail oject', () => {
const obj1 = {
handler() {},
deep: true
},
obj2 = {
handler() {}
};
let watchers = mapWatcher({
'testRequest, testRequest2': {
loading: obj1,
data: obj2
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': obj1,
'alovaHook$.testRequest.data': obj2,
'alovaHook$.testRequest2.loading': obj1,
'alovaHook$.testRequest2.data': obj2
});

watchers = mapWatcher({
'testRequest.loading, testRequest.data': obj1
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': obj1,
'alovaHook$.testRequest.data': obj1
});

watchers = mapWatcher({
'testRequest, testRequest2': {
'loading, data': obj1
}
});
expect(watchers).toStrictEqual({
'alovaHook$.testRequest.loading': obj1,
'alovaHook$.testRequest.data': obj1,
'alovaHook$.testRequest2.loading': obj1,
'alovaHook$.testRequest2.data': obj1
});
});
});
74 changes: 66 additions & 8 deletions test/useRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ describe('vue options request hook', () => {
params: { a: 'a', b: 'str' }
})
);
expect(successFn).toBeCalledTimes(1);
expect(completeFn).toBeCalledTimes(1);
expect(successFn).toHaveBeenCalledTimes(1);
expect(completeFn).toHaveBeenCalledTimes(1);
});
});

Expand Down Expand Up @@ -93,8 +93,8 @@ describe('vue options request hook', () => {
expect(screen.getByRole('loading')).toHaveTextContent('loaded');
expect(screen.getByRole('error')).toHaveTextContent('api error');
expect(screen.getByRole('data')).toHaveTextContent('{}');
expect(errorFn).toBeCalledTimes(1);
expect(completeFn).toBeCalledTimes(1);
expect(errorFn).toHaveBeenCalledTimes(1);
expect(completeFn).toHaveBeenCalledTimes(1);
});
});

Expand Down Expand Up @@ -122,8 +122,8 @@ describe('vue options request hook', () => {
expect(screen.getByRole('loading')).toHaveTextContent('loaded');
expect(screen.getByRole('error')).toHaveTextContent('');
expect(screen.getByRole('data')).toHaveTextContent('{}');
expect(successFn).not.toBeCalled();
expect(completeFn).not.toBeCalled();
expect(successFn).not.toHaveBeenCalled();
expect(completeFn).not.toHaveBeenCalled();

fireEvent.click(screen.getByRole('btnSend'));
await waitFor(() => {
Expand All @@ -136,8 +136,66 @@ describe('vue options request hook', () => {
params: {}
})
);
expect(successFn).toBeCalledTimes(1);
expect(completeFn).toBeCalledTimes(1);
expect(successFn).toHaveBeenCalledTimes(1);
expect(completeFn).toHaveBeenCalledTimes(1);
});
});

test('should get the right value in computed value and set computed value', async () => {
render(TestRequest as any, {
props: {
method: alovaInst.Get('/unit-test')
}
});

expect(screen.getByRole('loadingComputed')).toHaveTextContent('loading');
expect(screen.getByRole('dataComputed')).toHaveTextContent(JSON.stringify({}));
await waitFor(() => {
expect(screen.getByRole('loading')).toHaveTextContent('loaded');
const resJson = JSON.stringify({
path: '/unit-test',
method: 'GET',
params: {}
});
expect(screen.getByRole('data')).toHaveTextContent(resJson);
expect(screen.getByRole('loadingComputed')).toHaveTextContent('loaded');
expect(screen.getByRole('dataComputed')).toHaveTextContent(resJson);
});

fireEvent.click(screen.getByRole('btnModify'));
await waitFor(() => {
expect(screen.getByRole('dataComputed')).toHaveTextContent(
JSON.stringify({
modify: true
})
);
});
});

test('watch the hook state', async () => {
const watchFn = jest.fn();
render(TestRequest as any, {
props: {
method: alovaInst.Get('/unit-test', {
params: {
e: 'ee'
}
})
},
...eventObj({
watchState(data: any) {
watchFn(data);
}
})
});

await waitFor(() => {
expect(watchFn).toHaveBeenCalledTimes(2);
expect(watchFn).toHaveBeenCalledWith({
path: '/unit-test',
method: 'GET',
params: { e: 'ee' }
});
});
});

Expand Down
16 changes: 14 additions & 2 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StatesHook } from 'alova';
import { WatchHandler, WatchOptionsWithHandler } from 'vue';

type UseHookCallers = Record<string, Record<string, any>>;
type UseHookMapGetter<GR extends UseHookCallers> = (this: Vue, context: Vue) => GR;
Expand All @@ -21,7 +22,7 @@ interface VueHookMapperMixin<GR extends UseHookCallers> {
data(): {
[K in keyof GR]: PickFunction<GR[K], false>;
} & {
ALOVA_USE_HOOK_STATES$__: Record<string, any>;
alovaHook$: Record<string, any>;
};
methods: PickFunction<{
[K in FlattenObjectKeys<GR>]: K extends `${infer P}$${infer S}` ? GR[P][S] : never;
Expand All @@ -35,5 +36,16 @@ interface VueHookMapperMixin<GR extends UseHookCallers> {
*/
declare function mapAlovaHook<GR extends UseHookCallers>(mapGetter: UseHookMapGetter<GR>): VueHookMapperMixin<GR>[];

/** vue options statesHook */
type VueWatchHandler = WatchOptionsWithHandler<any> | WatchHandler<any>;
type AlovaWatcherHandlers = Record<string, VueWatchHandler | Record<string, VueWatchHandler>>;

/**
* 映射alova的useHook状态到watch对象上
* @param watcherHandlers watcher函数对象
*/
declare function mapAlovaWatcher(watcherHandlers: AlovaWatcherHandlers): Record<string, WatchOptionsWithHandler<any>>;

/**
* vue options statesHook
*/
declare const VueOptionsHook: StatesHook<unknown, unknown>;

0 comments on commit 47c22a7

Please sign in to comment.