Using requestAnimationFrame in React

2020-06-20 04:49发布

I am new to react native and I trying to optimize performance.

My Touch events are very slow and I was going through RN documentation on performance and they have mentioned to use requestAnimationFrame with this example

handleOnPress() {
  // Always use TimerMixin with requestAnimationFrame, setTimeout and
  // setInterval
  this.requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}

Now, this description sounds very vague and hard for me to comprehend its usage

Like, I have this touchable event in my RN app

<TouchableWithoutFeedback   onPress={() => this.touched(this.props.coinShortName)}>

Which calls this method

 touched = (id) => {

        this.setState({selectedPostId: id})
        if (this.props.coinShortName == this.state.selectedPostId ) { 
           this.setState({stateToDisplay: !this.state.stateToDisplay})
        }
    }

Question: Can someone please tell me on how I need to/should use it in my app?

3条回答
我想做一个坏孩纸
2楼-- · 2020-06-20 05:40

As mentioned , you need to wrap your expensive action in an instance of requestAnimationFrame.

Usage

<TouchableWithoutFeedback onPress={() => this.touched(this.props.coinShortName)}>

touched = (id) => {
  requestAnimationFrame(() => {
    this.setState({selectedPostId: id})
    if (this.props.coinShortName == this.state.selectedPostId ) {
     this.setState({stateToDisplay: !this.state.stateToDisplay})
    }
  });
}
查看更多
家丑人穷心不美
3楼-- · 2020-06-20 05:49

I'm going to convert my comment into an answer, so I can format it better and hopefully help someone in the future too. I don't expressly recommend marking my answer as correct, but I think this answer should be here alongside this question.

This article here should give you some backstory on requestAnimationFrame: http://www.javascriptkit.com/javatutors/requestanimationframe.shtml.

I would recommend reading the article I linked above and then read my answer after.

I will just explicitly mention that requestAnimationFrame could appear similar to setTimeout(() => {}, 0), but if you had a Zack Morris phone made in 1985, its "as soon as possible" might be 5 seconds later, thus making your animation look terrible, similar to when your character lags across the screen in a video game. The function may have been called at the correct time, but it did not actually execute at the correct time.

It can be helpful to imagine a collection phase and a rendering phase. I'm sorry, I don't know the exact terms for this stuff, but human eyes see smooth movement at I think 20 FPS, but what that means is you have 20 "frames", so it's like calculating something 20 times. It's like collecting a bunch of kids and pushing them into a bus 20 times per second. Pushing them into the bus is an event, and it's analogous to repainting your screen. Sometimes kids can get left behind and extra kids picked up next time, so you can imagine the gains to perceived smoothness of flow over time.

It's important to note that optimizations are being made with respect to the next repaint, or the next time the screen 'changes'. requestAnimationFrame does work under the hood to ensure the animation occurs at the correct time and is smooth, meaning the pixels were where they were supposed to be at the right time. (I think you would derive a lot of meaning if you checked out the definitions for "what is a janky animation", and look at some of the discussion around that. I mention that because we want to understand more about the repainting process and what kinds of things are important and why)

I recall that requestAnimationFrame can ditch calculations that would occur too late. For example, if you click the button and a pixel goes from 0% to 25% to 50% to 75% to 100% (some arbitrary distance calculation). We could say that after 1 second, the pixel should have travelled 50% of the distance and after 2 seconds, it should be at 100%, the final resting place.

It's more important that the pixels are in the correct place at the correct time than it is for them to travel to exactly every place they were supposed to. requestAnimationFrame is helping you do this. If the screen is about to repaint, and "it" needs to run a calculation that would take too long, "it" just ignores it and skips to the next frame. It's like trimming fat to keep on pace and therefore, avoid jank.

requestAnimationFrame is a solution for the same challenges whether it's in your web browser or iOS or Android. They all do this process of painting the screen over and over again. You could start calculating something needed for the next repaint but start too late so it's not done when the next repaint occurs.

Imagine your animation was smooth but your phone received 20 push notifications all of a sudden that bogged down the CPU, causing your animation to be delayed by 16.7 milliseconds. Rather than display the pixel at the correct place at the wrong time, requestAnimationFrame helps by making the pixel be in the correct place at the correct time, but it may do some magic and not even try to paint the pixel sometimes when it would have otherwise, thus saving performance and increasing perceived smoothness.

I just realized this is a wall of text, but I think it will be informational.

These repaints occur about 60 frames per second, so requestAnimationFrame could fire like 60 times a second when it calculates is the most optimal time. There are 1000 milliseconds in 1 second, so 60 FPS is one frame every 16.7ms. If the human eye perceives smoothness at 20FPS, then it means you could in theory repaint every 45ms or 30% as much, and the animation would still be smooth.

My definitions may be inaccurate, but I hope they can help give you a sense what is happening.

查看更多
唯我独甜
4楼-- · 2020-06-20 05:52
<TouchableWithoutFeedback onPress={this.handlePress}>

handlePress = () => {
   this.requestAnimationFrame(() => {
     this.touched(this.props.coinShortName)
   });
}

it would be event better if you removed the useless id parameter in the touched function by using directly this.props.coinShortName inside so you could write

handlePress = () => {
   this.requestAnimationFrame(this.touched);
} 

Anyway, the touched function doesn't seem to be really expensive so I don't know if it will solve your performance issue

查看更多
登录 后发表回答