refactor(ui): useAntdForm

This commit is contained in:
Fu Diwei
2024-12-25 14:51:32 +08:00
parent c9024c5611
commit 4d0f7c2e02
43 changed files with 779 additions and 677 deletions

View File

@@ -1,4 +1,5 @@
import useBrowserTheme from "./useBrowserTheme";
import useAntdForm from "./useAntdForm";
import useBrowserTheme from "./useBrowserTheme";
import useZustandShallowSelector from "./useZustandShallowSelector";
export { useBrowserTheme, useZustandShallowSelector };
export { useAntdForm, useBrowserTheme, useZustandShallowSelector };

106
ui/src/hooks/useAntdForm.ts Normal file
View File

@@ -0,0 +1,106 @@
import { useState } from "react";
import { Form, type FormInstance, type FormProps } from "antd";
import { useDeepCompareEffect } from "ahooks";
export interface UseAntdFormOptions<T extends NonNullable<unknown> = any> {
form?: FormInstance<T>;
initialValues?: Partial<T> | (() => Partial<T> | Promise<Partial<T>>);
onSubmit?: (values: T) => void | Promise<void>;
}
export interface UseAntdFormReturns<T extends NonNullable<unknown> = any> {
form: FormInstance<T>;
formProps: Omit<FormProps<T>, "children">;
formPending: boolean;
submit: (values?: T) => Promise<any>;
}
/**
*
* @param {UseAntdFormOptions} options
* @returns {UseAntdFormReturns}
*/
const useAntdForm = <T extends NonNullable<unknown> = any>({ initialValues, form, onSubmit }: UseAntdFormOptions<T>): UseAntdFormReturns<T> => {
const formInst = form ?? Form["useForm"]()[0];
const [formInitialValues, setFormInitialValues] = useState<Partial<T>>();
const [formPending, setFormPending] = useState(false);
useDeepCompareEffect(() => {
let unmounted = false;
if (!initialValues) {
return;
}
let temp: Promise<Partial<T>>;
if (typeof initialValues === "function") {
temp = Promise.resolve(initialValues());
} else {
temp = Promise.resolve(initialValues);
}
temp.then((temp) => {
if (!unmounted) {
type FieldName = Parameters<FormInstance<T>["getFieldValue"]>[0];
type FieldsValue = Parameters<FormInstance<T>["setFieldsValue"]>[0];
const obj = { ...temp };
Object.keys(temp).forEach((key) => {
obj[key as keyof T] = formInst!.isFieldTouched(key as FieldName) ? formInst!.getFieldValue(key as FieldName) : temp[key as keyof T];
});
setFormInitialValues(temp);
formInst!.setFieldsValue(obj as FieldsValue);
}
});
return () => {
unmounted = true;
};
}, [formInst, initialValues]);
const onFinish = (values: T) => {
if (formPending) return Promise.reject(new Error("Form is pending"));
setFormPending(true);
return new Promise((resolve, reject) => {
formInst
.validateFields()
.then(() => {
resolve(
Promise.resolve(onSubmit?.(values))
.then((data) => {
setFormPending(false);
return data;
})
.catch((err) => {
setFormPending(false);
throw err;
})
);
})
.catch((err) => {
setFormPending(false);
reject(err);
});
});
};
const formProps: FormProps = {
form: formInst,
initialValues: formInitialValues,
onFinish,
};
return {
form: formInst,
formProps: formProps,
formPending: formPending,
submit: () => {
return onFinish(formInst.getFieldsValue(true));
},
};
};
export default useAntdForm;

View File

@@ -1,5 +1,13 @@
import { useTheme } from "ahooks";
export default function () {
export type UseBrowserThemeReturns = ReturnType<typeof useTheme>;
/**
* 获取并设置当前浏览器系统主题。
* @returns {UseBrowserThemeReturns}
*/
const useBrowserTheme = (): UseBrowserThemeReturns => {
return useTheme({ localStorageKey: "certimate-ui-theme" });
}
};
export default useBrowserTheme;

View File

@@ -5,7 +5,28 @@ import { shallow } from "zustand/shallow";
type MaybeMany<T> = T | readonly T[];
export default function <T extends object, TKeys extends keyof T>(paths: MaybeMany<TKeys>): (state: T) => Pick<T, TKeys> {
export type UseZustandShallowSelectorReturns<T extends object, TKeys extends keyof T> = (state: T) => Pick<T, TKeys>;
/**
* 选择并获取指定的状态。
* 基于 `zustand.useShallow` 二次封装,以减少样板代码。
* @param {Array} paths 要选择的状态键名。
* @returns {UseZustandShallowSelectorReturns}
*
* @example
* ```js
* // 使用示例:
* const { foo, bar, baz } = useStore(useZustandShallowSelector(["foo", "bar", "baz"]));
*
* // 以上代码等效于:
* const { foo, bar, baz } = useStore((state) => ({
* foo: state.foo,
* bar: state.bar,
* baz: state.baz,
* }));
* ```
*/
const useZustandShallowSelector = <T extends object, TKeys extends keyof T>(paths: MaybeMany<TKeys>): UseZustandShallowSelectorReturns<T, TKeys> => {
const prev = useRef<Pick<T, TKeys>>({} as Pick<T, TKeys>);
return (state: T) => {
@@ -15,4 +36,6 @@ export default function <T extends object, TKeys extends keyof T>(paths: MaybeMa
}
return prev.current;
};
}
};
export default useZustandShallowSelector;