首页 > 解决方案 > 在 react hooks 中生成新的组件类型,如何保持性能?

问题描述

我正在探索一个生成包装器组件的钩子,以减少向我的 react-native 应用程序添加动画的样板。我已经写了一个返回动画值并在内部管理它们的钩子,它工作得很好。然而,每次我使用它时,我都必须编写应用动画值的样板,如下所示:

//pseudocode
const [animatedValue, animationRef, ...otherUsefuLStuff] = useMyGenericAnimationHook(...some animation config)

return (
    <Animated.View style={{transform: [{translateY: animatedValue}]}}>
        .....
    </Animated.View>
)

这没什么大不了的,但它仍然是我必须为每个动画添加的一些样板。我正在考虑是否可以从我的动画钩子中返回一个包装器组件来抽象出这个样板,如下所示

//pseudocode
const [AnimatedTranslateY, animatedValue, animationRef, ...otherUsefulStuff] = useMyGenericAnimationHook(... some animation config)

return (
    <AnimatedTranslateY>.....</AnimatedTranslateY>
)

这看起来更简洁,但是从钩子返回一个新的组件类型肯定会导致问题。React 将在每次渲染时拆除并重建子组件,因为每次 useMyAnimationHook() 运行时都会返回一个新的组件类型!

我最初的想法是在我的钩子中记住返回的组件类型

//pseudocode
const useMyGenericAnimationHook= (....configuration arguments) => {
    const animatedValueRef = ....create the animated value(s) ref(s)

    const WrapperView: React.FC<ViewProps> = useMemo(
        () =>
            React.memo(({ children, style, ...restProps }) => (
                <Animated.View {...restProps} 
                    style={[style, { transform: ...apply animated value(s)}]}>
                    {children}
                </Animated.View>
            )),
        [animatedValueRef.current]
    )

    return [WrapperView, animationHandle, otherUsefulStuff]
}

这就是我有些困惑的地方。我认为这应该可以正常工作,而不是在每次渲染时重建树。组件类型应该保持稳定,除非给 useMemo 的依赖项在我们希望它渲染的时候发生变化。然而,我对此并不完全有信心。

使用时反应的行为是什么<WrapperView>

这不是一个好的模式有什么理由吗?

感谢您的任何见解!

标签: reactjsreact-nativereact-hooks

解决方案


这里还有一个想法。我将稍微调整一下你的钩子 API,这样你就不会返回新组件(只是向Animated.View组件添加道具),而是返回一组返回包装器道具的函数。然后,您调用所需的函数并将结果合并到组件的样式属性中。例子:

function useMyGenericAnimationHook(...animationConfig) {
  let animatedValue, animationRef, otherUsefulStuff;
  return {
    animatedValue,
    animationRef,
    ...otherUsefulStuff,
    // here's the meat of it
    translateY: () => ({
      transform: [{translateY: animatedValue}]
    }),
    color: () => ({
      color: `hsl(120,100%,${animatedValue}%)`
    }),
  };
}

function Component() {
  let { translateY, color } = useMyGenericAnimationHook(...animationConfig);
  return (
    <Animated.View style={{ ...translateY(), ...color() }}>
      {children}
    </Animated.View>
  );
}

旧答案

我喜欢这种模式——它看起来很干净。只要您确保它不会在每次渲染时生成新的组件类型,它就应该是高性能的,并且useMemo应该为此工作。

另一种选择是将组件移到挂钩之外。如果这适用于您的用例,则它保证渲染之间的引用相等。但是,它不允许您将道具绑定到组件,因此用户必须提供任何必需的道具。

//pseudocode
const WrapperView = React.memo(({ children, style, ...restProps }: ViewProps) => (
  <Animated.View {...restProps} 
                 style={[style, { transform: ...apply animated value(s)}]}>
                 {children}
  </Animated.View>
);

const useMyGenericAnimationHook= (....configuration arguments) => {
    const animatedValueRef = ....create the animated value(s) ref(s)

    return [WrapperView, animationHandle, otherUsefulStuff]
}

第三种但更笨拙的选择是从钩子中返回 JSX。

function useDiv() {
  return <div />;
}
function Component(props) {
  const div = useDiv();
  return (
    <main>{div}</main> // equivalent to <main><div/></main>
  )
}

您基本上可以将 JSX 复制粘贴到呈现的输出中,这可能适合您的用例。不过,如果前两个选项都不适合您,我只会使用它。


推荐阅读