React loading HOC:列表中提供的所有道具都被设置为可选,如果其中任何一个未定义,则显示一个旋转器而不是组



我想创建一个HOC,在那里我可以提供一个道具数组,并检查它们是否未定义:如果是这样,则显示带有旋转器的加载组件,而不是包装的组件。

这是比较容易的部分。

因为这些道具不能是未定义的(否则会显示旋转器),所以我希望它们是可选的道具:

const MyComponentWithLoading = withLoading(MyComponent);
return (
<MyComponentWithLoading
loadingFields={['user']}
user={user} // MyComponent requires "user" to not be undefined, but since it's in "loadingFields" it can actually be undefined
device={device} // MyComponent requires "device" to not be undefined and it must be so because it's not in "loadingFields"
/>
);

我尝试了下面的代码,但它不起作用。有什么办法吗?

import React, {FC, ComponentType, PropsWithChildren} from 'react';
import Loading from '@components/Loading';
type WithLoadingProps<T> = {
loadingFields?: Array<
keyof Omit<PropsWithChildren<T & WithLoadingProps<T>>, 'loadingFields'>
>;
};
const withLoading =
<T,>(
Component: ComponentType<T>,
): FC<
Partial<Pick<T, keyof WithLoadingProps<T>['loadingFields']>> &
Omit<T, keyof WithLoadingProps<T>['loadingFields']> &
WithLoadingProps<T>
> =>
({loadingFields: loadingData = [], ...otherProps}) => {
const isLoading = (): boolean => {
for (const key of loadingData) {
if (otherProps[key] === undefined) {
return true;
}
}
return false;
};
return isLoading() ? <Loading /> : <Component {...(otherProps as T)} />;
};
export default withLoading;

我认为组件上的loadingFields属性最好作为高阶函数withLoading的参数。

让我们先定义一些类型:

type AllowUndefined<T> = {
[P in keyof T]: T[P] | undefined;
};
type SomeUndefined<T, A extends keyof T> = Omit<T, A> &
AllowUndefined<Pick<T, A>>;

SomeUndefined类型允许我们使类型中的某些字段接受undefined作为值。

对于type

type A = {
a:number;
b:string;
}

type B = SomeUndefined<A, 'a'>将生成一个类型

{
a:number|undefined
b:string
}

这将在以后有用。

现在让我们用上面讨论的附加参数定义withLoading:
const withLoading = <T, A extends keyof T>(
Component: ComponentType<T>,
// readonly is important here so type-inference works correctly
// require at least one value otherwise t-i breaks down
loadingFields: readonly [A, ...A[]], 
): FC<SomeUndefined<T, A>> => (props) => {
const isLoading: boolean = Object.entries(props).some(
([k, v]) => loadingFields.includes(k as A) && typeof v === "undefined",
);
return isLoading ? (
<Loading />
) : (
<Component {...(props as PropsWithChildren<T>)} />
);
};
现在,假设我们有一个简单的组件:
const Foo: FC<{ a: number; b: string }> = ({ a, b }) => (
<div>
{[...Array(a)].map((_, i) => (
<div key={i}>{b}</div>
))}
</div>
);

我们可以把它包装起来,让一些字段是可选的:

const LoadingFoo = withLoading(Foo, ["a"]);

意味着我们现在可以使用这个新组件了:

<LoadingFoo a={undefined} b="hello" />

,我们会看到一个加载消息,

否则:

<LoadingFoo a={10} b="hello" />

,我们将看到一个已部署的组件。耶!

CodeSandbox例子

编辑或者,如果你真的想把loadingFields道具放在"extended"组件,你可以:

function withLoadingOnComponent<T>(Component: ComponentType<T>) {
return <A extends keyof T>({
loadingFields,
...props
}: PropsWithChildren<
SomeUndefined<T, A> & { loadingFields: readonly [A, ...A[]] }
>): JSX.Element => {
const isLoading: boolean = Object.entries(props).some(
([k, v]) => loadingFields.includes(k as A) && typeof v === "undefined",
);
return isLoading ? <Loading /> : <Component {...(props as any)} />;
};
}

然后

const FWC = withLoadingOnComponent(Foo);

所以你可以:

<FWC loadingFields={["a"]} a={undefined} b={"hello"} />

我还没有想出如何使它成为可选的。

最新更新