Я изучаю 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, чтобы позволить вместо 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