简体   繁体   English

React hooks函数组件阻止在状态更新时重新呈现

[英]React hooks function component prevent re-render on state update

I'm learning about React Hooks which means I'm going to have to move away from classes to function components. 我正在学习React Hooks,这意味着我将不得不从类转向功能组件。 Previously in classes I could have class variables independent of the state that I could update without the component re-rendering. 以前在类中,我可以拥有独立于我可以在不重新呈现组件的情况下更新的状态的类变量。 Now that I am attempting to re-create a component as a function component with hooks I have ran into the problem that I can't (as far as I know) make variables for that function so the only way to store data is through the useState hook. 现在我试图用钩子重新创建一个组件作为一个函数组件,我遇到了一个问题,我不能(据我所知)为该函数创建变量,因此存储数据的唯一方法是通过useState钩子。 However this means my component will re-render whenever that state is updated. 但是,这意味着无论何时更新该状态,我的组件都将重新呈现。

I've illustrated it in the example below where I attempted to re-create a class component as a function component that uses hooks. 我在下面的示例中说明了这一点,我试图将类组件重新创建为使用钩子的函数组件。 I want to animate a div if someone clicks on it, but prevent the animation from being called again if the user clicks while it's already animating. 如果有人点击它,我想为div设置动画,但是如果用户点击它已经动画,则阻止动画再次被调用。

 class ClassExample extends React.Component { _isAnimating = false; _blockRef = null; onBlockRef = (ref) => { if (ref) { this._blockRef = ref; } } // Animate the block. onClick = () => { if (this._isAnimating) { return; } this._isAnimating = true; Velocity(this._blockRef, { translateX: 500, complete: () => { Velocity(this._blockRef, { translateX: 0, complete: () => { this._isAnimating = false; } }, { duration: 1000 }); } }, { duration: 1000 }); }; render() { console.log("Rendering ClassExample"); return( <div> <div id='block' onClick={this.onClick} ref={this.onBlockRef} style={{ width: '100px', height: '10px', backgroundColor: 'pink'}}>{}</div> </div> ); } } const FunctionExample = (props) => { console.log("Rendering FunctionExample"); const [ isAnimating, setIsAnimating ] = React.useState(false); const blockRef = React.useRef(null); // Animate the block. const onClick = React.useCallback(() => { if (isAnimating) { return; } setIsAnimating(true); Velocity(blockRef.current, { translateX: 500, complete: () => { Velocity(blockRef.current, { translateX: 0, complete: () => { setIsAnimating(false); } }, { duration: 1000 }); } }, { duration: 1000 }); }); return( <div> <div id='block' onClick={onClick} ref={blockRef} style={{ width: '100px', height: '10px', backgroundColor: 'red'}}>{}</div> </div> ); }; ReactDOM.render(<div><ClassExample/><FunctionExample/></div>, document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root' style='width: 100%; height: 100%'> </div> 

If you click on the ClassExample bar(pink) you will see that it does not re-render while animating, however if you click on the FunctionExample bar(red) it will rerender twice while it is animating. 如果单击ClassExample栏(粉红色),您将看到它在动画时不会重新渲染,但是如果单击FunctionExample栏(红色),它将在动画时重新渲染两次。 This is because I'm using setIsAnimating which causes the re-render. 这是因为我正在使用setIsAnimating导致重新渲染。 I know it's probably not very performance-winning but I would like to prevent it if it's at all possible with a function component. 我知道它可能不是非常有性能的,但我想在功能组件完全可能的情况下阻止它。 Any suggestions/am I doing something wrong? 任何建议/我做错了什么?

Update (attempted fix, no solution yet): Below user lecstor suggested possibly changing the result of useState to let instead of const and then setting it directly let [isAnimating] = React.useState(false); 更新(尝试修复,还没有解决方案):在用户lecstor下面建议可能更改useState的结果而不是const然后直接设置它let [isAnimating] = React.useState(false); . This unfortunately does not work either as you can see in the snippet below. 遗憾的是,这不会起作用,因为您可以在下面的代码段中看到。 Clicking on the red bar will start its animation, clicking on the orange square will make the component re-render, and if you click on the red bar again it will print that isAnimating is reset to false even though the bar is still animating. 单击红色栏将开始它的动画,点击橙色方块将使组件重新渲染,如果你单击红色的酒吧再次将打印isAnimating重新设置为false,即使栏仍是动画。

 const FunctionExample = () => { console.log("Rendering FunctionExample"); // let isAnimating = false; // no good if component rerenders during animation // abuse useState var instead? let [isAnimating] = React.useState(false); // Var to force a re-render. const [ forceCount, forceUpdate ] = React.useState(0); const blockRef = React.useRef(null); // Animate the block. const onClick = React.useCallback(() => { console.log("Is animating: ", isAnimating); if (isAnimating) { return; } isAnimating = true; Velocity(blockRef.current, { translateX: 500, complete: () => { Velocity(blockRef.current, { translateX: 0, complete: () => { isAnimating = false; } }, { duration: 5000 }); } }, { duration: 5000 }); }); return ( <div> <div id = 'block' onClick = {onClick} ref = {blockRef} style = { { width: '100px', height: '10px', backgroundColor: 'red' } } > {} </div> <div onClick={() => forceUpdate(forceCount + 1)} style = { { width: '100px', height: '100px', marginTop: '12px', backgroundColor: 'orange' } }/> </div> ); }; ReactDOM.render( < div > < FunctionExample / > < /div>, document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root' style='width: 100%; height: 100%'> </div> 

Update 2(solution): If you want to have a variable in a function component but not have it re-render the component when it's updated, you can use useRef instead of useState . 更新2(解决方案):如果您希望在函数组件中有变量但在更新时不重新渲染组件,则可以使用useRef而不是useState useRef can be used for more than just dom elements and is actually suggested to be used for instance variables. useRef可用于dom元素,实际上建议用于实例变量。 See: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables 请参阅: https//reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

Use a ref to keep values between function invocations without triggering a render 使用ref在函数调用之间保持值而不触发渲染

 class ClassExample extends React.Component { _isAnimating = false; _blockRef = null; onBlockRef = (ref) => { if (ref) { this._blockRef = ref; } } // Animate the block. onClick = () => { if (this._isAnimating) { return; } this._isAnimating = true; Velocity(this._blockRef, { translateX: 500, complete: () => { Velocity(this._blockRef, { translateX: 0, complete: () => { this._isAnimating = false; } }, { duration: 1000 }); } }, { duration: 1000 }); }; render() { console.log("Rendering ClassExample"); return( <div> <div id='block' onClick={this.onClick} ref={this.onBlockRef} style={{ width: '100px', height: '10px', backgroundColor: 'pink'}}>{}</div> </div> ); } } const FunctionExample = (props) => { console.log("Rendering FunctionExample"); const isAnimating = React.useRef(false) const blockRef = React.useRef(null); // Animate the block. const onClick = React.useCallback(() => { if (isAnimating.current) { return; } isAnimating.current = true Velocity(blockRef.current, { translateX: 500, complete: () => { Velocity(blockRef.current, { translateX: 0, complete: () => { isAnimating.current = false } }, { duration: 1000 }); } }, { duration: 1000 }); }); return( <div> <div id='block' onClick={onClick} ref={blockRef} style={{ width: '100px', height: '10px', backgroundColor: 'red'}}>{}</div> </div> ); }; ReactDOM.render(<div><ClassExample/><FunctionExample/></div>, document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root' style='width: 100%; height: 100%'> </div> 

Why is it that you think you can't use internal variables in the same way as you do with classes? 为什么你认为你不能像使用类一样使用内部变量?

ok, feels a bit dirty but how about mutating the useState state? 好吧,感觉有点脏,但如何改变useState状态? 8) 8)

nope, that doesn't work as intended state is reset on re-render nope,在重新渲染时重置不起预期状态的状态

So as this is not related to the actual rendering of the component, maybe our logic needs to be based on the animation itself. 因此,这与组件的实际渲染无关,也许我们的逻辑需要基于动画本身。 This particular problem can be resolved by checking the class that velocity sets on the element while it is being animated. 这个特殊问题可以通过检查元素在动画时设置的类来解决。

 const FunctionExample = ({ count }) => { console.log("Rendering FunctionExample", count); // let isAnimating = false; // no good if component rerenders during animation // abuse useState var instead? // let [isAnimating] = React.useState(false); const blockRef = React.useRef(null); // Animate the block. const onClick = React.useCallback(() => { // use feature of the anim itself if (/velocity-animating/.test(blockRef.current.className)) { return; } console.log("animation triggered"); Velocity(blockRef.current, { translateX: 500, complete: () => { Velocity(blockRef.current, { translateX: 0, }, { duration: 1000 }); } }, { duration: 5000 }); }); return ( <div> <div id = 'block' onClick = {onClick} ref = {blockRef} style = { { width: '100px', height: '10px', backgroundColor: 'red' } } > {} </div> </div> ); }; const Counter = () => { const [count, setCount] = React.useState(0); return <div> <FunctionExample count={count} /> <button onClick={() => setCount(c => c + 1)}>Count</button> </div>; } ReactDOM.render( < div > < Counter / > < /div>, document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root' style='width: 100%; height: 100%'> </div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM