refactor(ui): useAntdForm
This commit is contained in:
@@ -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
106
ui/src/hooks/useAntdForm.ts
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user