使用挂钩取消React组件中的requestAnimationRequest不起作用

时间:2020-06-30 08:17:32

标签: javascript reactjs frontend react-hooks requestanimationframe

我正在进度条上(最终是..),我想在达到某个值(10、100,...,N)时停止动画(调用cancelAnimationRequest)并将其重置为0。

但是,使用我当前的代码,它会重置为0,但会无限期地运行。我认为这部分代码可能有问题:

setCount((prevCount) => {
    console.log('requestRef.current', requestRef.current, prevCount);
    
    if (prevCount < 10) return prevCount + deltaTime * 0.001;
    
    // Trying to cancel the animation here and reset to 0:
    cancelAnimationFrame(requestRef.current);

    return 0;
});

这是整个示例:

const Counter = () => {
  const [count, setCount] = React.useState(0);

  // Use useRef for mutable variables that we want to persist
  // without triggering a re-render on their change:
  const requestRef = React.useRef();
  const previousTimeRef = React.useRef();

  const animate = (time) => {
    if (previousTimeRef.current != undefined) {
      const deltaTime = time - previousTimeRef.current;

      // Pass on a function to the setter of the state
      // to make sure we always have the latest state:
      setCount((prevCount) => {
        console.log('requestRef.current', requestRef.current, prevCount);
        
        if (prevCount < 10) return prevCount + deltaTime * 0.001;
        
        // Trying to cancel the animation here and reset to 0:
        cancelAnimationFrame(requestRef.current);

        return 0;
      });
    }
    
    previousTimeRef.current = time;
    requestRef.current = requestAnimationFrame(animate);
  }

  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []);

  return <div>{ Math.round(count) }</div>;
}

ReactDOM.render(<Counter />, document.getElementById('app'));
html {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

body {
  font-size: 60px;
  font-weight: 700;
  font-family: 'Roboto Mono', monospace;
  color: #5D9199;
  background-color: #A3E3ED;
}

.as-console-wrapper {
  max-height: 66px !important;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

密码笔:https://codepen.io/fr-nevin/pen/RwrLmPd

1 个答案:

答案 0 :(得分:1)

代码的主要问题是您试图取消已经执行的更新。相反,您可以避免请求不需要的最后一次更新。您可以在下面看到问题和简单的解决方案:

const Counter = () => {
  const [count, setCount] = React.useState(0);
  const requestRef = React.useRef();
  const previousTimeRef = React.useRef(0);

  const animate = React.useCallback((time) => {
    console.log('       RUN:', requestRef.current);

    setCount((prevCount) => {  
      const deltaTime = time - previousTimeRef.current;   
      const nextCount = prevCount + deltaTime * 0.001;
      
      // We add 1 to the limit value to make sure the last valid value is
      // also displayed for one whole "frame":
      if (nextCount >= 11) {
        console.log('    CANCEL:', requestRef.current, '(this won\'t work as inteneded)');
        
        // This won't work:
        // cancelAnimationFrame(requestRef.current);
        
        // Instead, let's use this Ref to avoid calling `requestAnimationFrame` again:
        requestRef.current = null;
      }
      
      return nextCount >= 11 ? 0 : nextCount;
    });
    
    // If we have already reached the limit value, don't call `requestAnimationFrame` again:
    if (requestRef.current !== null) {
      previousTimeRef.current = time;
      requestRef.current = requestAnimationFrame(animate);
      console.log('- SCHEDULE:', requestRef.current);
    }     
  }, []);

  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []);
  
  // This floors the value:
  // See https://stackoverflow.com/questions/7487977/using-bitwise-or-0-to-floor-a-number.
  return (<div>{ count | 0 } / 10</div>);
};

ReactDOM.render(<Counter />, document.getElementById('app'));
html {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

body {
  font-size: 60px;
  font-weight: 700;
  font-family: 'Roboto Mono', monospace;
  color: #5D9199;
  background-color: #A3E3ED;
}

.as-console-wrapper {
  max-height: 66px !important;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

在任何情况下,您更新状态的次数都比实际需要的次数多,这可以通过使用timerequestAnimationFrame提供的时间戳(requestAnimationFrame)来避免跟踪当前和下一个/目标计数器值。您仍将多次调用setCount(...)更新函数,但是只有在知道更改将反映在UI中后,才更新状态(const Counter = ({ max = 10, rate = 0.001, location }) => { const limit = max + 1; const [count, setCount] = React.useState(0); const t0Ref = React.useRef(Date.now()); const requestRef = React.useRef(); const targetValueRef = React.useRef(1); const animate = React.useCallback(() => { // No need to keep track of the previous time, store initial time instead. Note we can't // use the time param provided by requestAnimationFrame to the callback, as that one won't // be reset when the `location` changes: const time = Date.now() - t0Ref.current; const nextValue = time * rate; if (nextValue >= limit) { console.log('Reset to 0'); setCount(0); return; } const targetValue = targetValueRef.current; if (nextValue >= targetValue) { console.log(`Update ${ targetValue - 1 } -> ${ nextValue | 0 }`); setCount(targetValue); targetValueRef.current = targetValue + 1; } requestRef.current = requestAnimationFrame(animate); }, []); React.useEffect(() => { requestRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(requestRef.current); }, []); React.useEffect(() => { // Reset counter if `location` changes, but there's no need to call `cancelAnimationFrame` . setCount(0); t0Ref.current = Date.now(); targetValueRef.current = 1; }, [location]); return (<div className="counter">{ count } / { max }</div>); }; const App = () => { const [fakeLocation, setFakeLocation] = React.useState('/'); const handleButtonClicked = React.useCallback(() => { setFakeLocation(`/${ Math.random().toString(36).slice(2) }`); }, []); return (<div> <span className="location">Fake Location: { fakeLocation }</span> <Counter max={ 10 } location={ fakeLocation } /> <button className="button" onClick={ handleButtonClicked }>Update Parent</button> </div>); }; ReactDOM.render(<App />, document.getElementById('app'));)。

html {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

body {
  font-family: 'Roboto Mono', monospace;
  color: #5D9199;
  background-color: #A3E3ED;
}

.location {
  font-size: 16px;
}

.counter {
  font-size: 60px;
  font-weight: 700;
}

.button {
  border: 2px solid #5D9199;
  padding: 8px;
  margin: 0;
  font-family: 'Roboto Mono', monospace;
  color: #5D9199;
  background: transparent;
  outline: none;
}

.as-console-wrapper {
  max-height: 66px !important;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
@Configuration
public class AppConfig {
    @Bean
    public DemoClass service() 
    {
        
    }
}