React的`memo`将泛型放入返回的函数中

时间:2019-07-04 15:47:52

标签: reactjs typescript

我想对带有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}`} />
  </>
);

是否有解决此问题的更优雅的主意?

4 个答案:

答案 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)
}

语法可能有点差,但是您明白了