我正在学习React Hooks,这意味着我将不得不从类转移到功能组件。以前在类中,我可以拥有与状态无关的类变量,而无需重新渲染组件即可更新状态。现在,我试图用挂钩将组件重新创建为功能组件,但我遇到了一个问题,据我所知,我无法为该函数创建变量,因此存储数据的唯一方法是通过useState
钩。但是,这意味着只要状态更新,我的组件就会重新呈现。
我已在下面的示例中说明了该示例,在该示例中,我尝试将类组件重新创建为使用钩子的功能组件。如果有人单击它,我想给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>
如果单击ClassExample栏(粉红色),则会在制作动画时看到它不会重新渲染,但是,如果单击FunctionExample栏(红色),它将在制作动画时重新渲染两次。这是因为我正在使用setIsAnimating
导致重新渲染。我知道这可能不是很能赢得性能,但是如果功能组件完全有可能的话,我想阻止它。有任何建议/我做错了什么吗?
更新(尝试修复,尚无解决方案):
下面的用户lecstor建议可能将useState的结果更改为let而不是const,然后直接将其设置为let [isAnimating] = React.useState(false);
。不幸的是,这也无法正常工作,如下面的代码片段所示。单击红色条将开始其动画,单击橙色方块将重新渲染组件,如果再次单击红色条,它将打印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>
更新2(解决方案):
如果要在函数组件中包含变量,但不希望变量在更新时重新呈现,则可以使用useRef
代替useState
。 useRef
不仅可以用于dom元素,而且实际上建议用于实例变量。
参见:https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
答案 0 :(得分:2)
使用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>
答案 1 :(得分:1)
为什么您认为不能像使用类一样使用内部变量?
好的,感觉有点脏,但是如何改变useState状态呢? 8)
是的,不能按预期工作 重新渲染时重置状态
由于这与组件的实际呈现无关,因此我们的逻辑可能需要基于动画本身。通过检查在动画中元素上的速度设置的类,可以解决此特定问题。
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>