我想对带有generic参数的函数使用React的memo
。不幸的是,泛型参数默认为泛型,所有奇特的泛型推导逻辑都丢失了(TypeScript v3.5.2)。在下面的示例中,WithMemo
(使用React.memo
)失败并显示:
Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
WithoutMemo
正常工作。
interface TProps<T extends string | number> {
arg: T;
output: (o: T) => string;
}
const Test = <T extends string | number>(props: TProps<T>) => {
const { arg, output } = props;
return <div>{output(arg)} </div>;
};
const myArg = 'a string';
const WithoutMemo = <Test arg={myArg} output={o => `${o}: ${o.length}`} />;
const MemoTest = React.memo(Test);
const WithMemo = <MemoTest arg={myArg} output={o => `${o}: ${o.length}`} />;
我看过this question,但我认为这与我的问题无关。
我发现了使用通用接口的可能解决方案,但似乎有些粗糙:
const myArgStr = 'a string';
const myArgNo: number = 2;
const WithoutMemo = (
<>
<Test arg={myArgStr} output={o => `${o}: ${o.length}`} />
<Test arg={myArgNo} output={o => `${o * 2}`} />
</>
);
interface MemoHelperFn {
<T extends string | number>(arg: TProps<T>): JSX.Element;
}
const MemoTest: MemoHelperFn = React.memo(Test);
const WithMemo = (
<>
<MemoTest arg={myArgStr} output={o => `${o}: ${o.length}`} />
<MemoTest arg={myArgNo} output={o => `${o * 2}`} />
</>
);
// Below fail as expected
const FailsWithoutMemo = (
<>
<Test arg={myArgNo} output={o => `${o}: ${o.length}`} />
<Test arg={myArgStr} output={o => `${o * 2}`} />
</>
);
const FailsWithMemo = (
<>
<MemoTest arg={myArgNo} output={o => `${o}: ${o.length}`} />
<MemoTest arg={myArgStr} output={o => `${o * 2}`} />
</>
);
是否有解决此问题的更优雅的主意?
答案 0 :(得分:1)
作为一种解决方法,我们可以在组件内部使用useMemo。应该足够好了。
答案 1 :(得分:1)
要详细说明上述答案,您可以使用比较浅的方式创建自己的备忘录挂钩。它仍然可以避免不必要地渲染组件(和任何子组件)。有点儿冗长,但这是我到目前为止发现的最佳解决方法。
import { ReactElement, useRef } from 'react'
const shallowEqual = <Props extends object>(left: Props, right: Props) => {
if (left === right) {
return true
}
const leftKeys = Object.keys(left)
const rightKeys = Object.keys(right)
if (leftKeys.length !== rightKeys.length) {
return false
}
return leftKeys.every(key => (left as any)[key] === (right as any)[key])
}
export const useMemoRender = <Props extends object>(
props: Props,
render: (props: Props) => ReactElement,
): ReactElement => {
const propsRef = useRef<Props>()
const elementRef = useRef<ReactElement>()
if (!propsRef.current || !shallowEqual(propsRef.current, props)) {
elementRef.current = render(props)
}
propsRef.current = props
return elementRef.current as ReactElement
}
然后您的代码变为
interface TProps<T extends string | number> {
arg: T
output: (o: T) => string
}
const Test = <T extends string | number>(props: TProps<T>): ReactElement => {
const { arg, output } = props
return <div>{output(arg)}</div>
}
const MemoTest = <T extends string | number>(props: TProps<T>) =>
useMemoRender(props, Test)
答案 2 :(得分:1)
来自https://stackoverflow.com/a/60170425/1747471
interface ISomeComponentWithGenericsProps<T> { value: T; }
function SomeComponentWithGenerics<T>(props: ISomeComponentWithGenericsProps<T>) {
return <span>{props.value}</span>;
}
export default React.memo(SomeComponentWithGenerics) as typeof SomeComponentWithGenerics;
答案 3 :(得分:0)
一种选择是编写自己的HOC,其中包括一个泛型并集成React.memo
。
function Memoized<T>(Wrapped) {
const component: React.FC<T> = props => <Wrapped {...props} />
return React.memo(component)
}
语法可能有点差,但是您明白了