react hooks中生成新的组件类型,如何保持性能? [英] Generating new component types in react hooks, how to maintain performance?

查看:55
本文介绍了react hooks中生成新的组件类型,如何保持性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

//伪代码const [animatedValue, animationRef, ...otherUsefuLStuff] = useMyGenericAnimationHook(...一些动画配置)返回 (<Animated.View style={{transform: [{translateY: animationValue}]}}>.....</Animated.View>)

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

//伪代码const [AnimatedTranslateY,animatedValue, animationRef, ...otherUsefulStuff] = useMyGenericAnimationHook(...一些动画配置)返回 (<AnimatedTranslateY>.....</AnimatedTranslateY>)

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

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

//伪代码const useMyGenericAnimationHook= (....配置参数) =>{const animationValueRef = ....create the animation value(s) ref(s)const WrapperView:React.FC= 使用备忘录(() =>React.memo(({ children, style, ...restProps }) => (<Animated.View {...restProps}style={[style, { transform: ...应用动画值}]}>{孩子们}</Animated.View>)),[animatedValueRef.current])返回 [WrapperView、animationHandle、otherUsefulStuff]}

这就是我有点困惑的地方.我认为这应该可以正常工作,并且不会在每次渲染时重建树.组件类型应该保持稳定,除非给 useMemo 的依赖发生变化,我们希望它无论如何都能渲染.不过,我对此并不完全有信心.

当使用 时,react 的行为是什么?

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

感谢您的任何见解!

解决方案

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

function useMyGenericAnimationHook(...animationConfig) {让动画值、动画引用、其他有用的东西;返回 {动画值,动画参考,...其他有用的东西,//这是它的肉translateY: () =>({变换:[{translateY:animatedValue}]}),颜色:() =>({颜色:`hsl(120,100%,${animatedValue}%)`}),};}功能组件(){让 { translateY, color } = useMyGenericAnimationHook(...animationConfig);返回 (<Animated.View style={{ ...translateY(), ...color() }}>{孩子们}</Animated.View>);}

旧答案

我喜欢这个图案——它看起来很干净.只要您确保它不会在每次渲染时生成新的组件类型,它就应该是高性能的,并且 useMemo 应该可以做到这一点.

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

//伪代码const WrapperView = React.memo(({ children, style, ...restProps }: ViewProps) => (<Animated.View {...restProps}style={[style, { transform: ...应用动画值}]}>{孩子们}</Animated.View>);const useMyGenericAnimationHook= (....配置参数) =>{const animationValueRef = ....create the animation value(s) ref(s)返回 [WrapperView、animationHandle、otherUsefulStuff]}

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

function useDiv() {返回 

;}功能组件(道具){const div = useDiv();返回 (<main>{div}</main>//等价于<main><div/></main>)}

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

I'm exploring a hook that generates a wrapper component to reduce the boilerplate of adding animations to my react-native app. I already wrote a hook that returns animated values and takes care of managing them internally, it works great. Each time I use it however, I have to write the boilerplate of applying the animated values like so:

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

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

This isn't a big deal, but it's still some boilerplate I have to put in for each animation. I was considering if I could return a wrapper component from my animation hook to abstract away this boilerplate, something like the following

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

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

This looks cleaner, but returning a new component type from a hook definitely causes a problem. React will tear down and rebuild the children on each render because each time useMyAnimationHook() runs it will return a new component type!

My initial idea is to memoize the returned component type like so inside my hook

//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]
}

Here's where I'm somewhat confused. I think that this should work fine and not rebuild the tree on each render. The component type should remain stable unless the dependencies given to useMemo change at which point we want it to render anyway. I'm not totally confident about this however.

What will react's behavior be when using <WrapperView>?

Is there any reason why this isn't a good pattern?

Thanks for any insight!

解决方案

Here's one more idea. I'm going to tweak your hook API a bit so that instead of returning new components (that just add props to the Animated.View component), you return a set of functions that return the wrapper props. Then, you call the functions you want and merge the results onto the style prop of a component. Example:

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>
  );
}

Old Answer

I like this pattern--it looks very clean. As long as you make sure it doesn't generate a new component type each render it should be performant, and useMemo should work for that.

Another option would be to move the component outside the hook. If that works for your use case, it guarantees referential equality between renders. However, it doesn't let you bind props to the component, so the user must supply any required props.

//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]
}

A third, but more unwieldy option would be to return JSX from the hook.

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

You can essentially copy-paste JSX into the rendered output, which might suit your use case. I would only use this if neither of the first two options work for you, though.

这篇关于react hooks中生成新的组件类型,如何保持性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆