Я исправил пример здесь - https://codesandbox.io/s/2w0oy6qnvn
В вашем примере хуков было несколько проблем:
setPosition
отличается от setState
,Он не выполняет поверхностное объединение, он заменяет весь объект новым значением, поэтому для объединения с предыдущим значением вам нужно использовать Object.assign()
или оператор распространения.Кроме того, хук setPosition()
принимает значение обратного вызова, которое предоставляет предыдущее значение состояния в качестве первого параметра, если вам нужно ссылаться на него при установке нового значения.
В отличие от классов,Функция handleMouseMove
воссоздается при каждом рендеринге, поэтому document.removeEventListener('mousemove', handleMouseMove)
больше не ссылается на начальное значение handleMouseMove
при вызове document.addEventListener('mousemove', handleMouseMove)
.Обходной путь для этого должен использовать useRef
, который создает объект, который сохраняется в течение всего срока службы компонента, идеально подходит для сохранения ссылки на функции.
Событиепараметр в handleMouseDown
и тот, на который вы ссылаетесь в setPosition
, не совпадают.Поскольку React использует пул событий и повторно использует события, событие в setPosition
может уже отличаться от события, переданного в handleMouseDown
.Чтобы обойти это, сначала нужно получить значения pageX
и pageY
, чтобы в setPosition
не нужно было полагаться на объект события.
Я аннотировалкод ниже с частями, которые вы должны принять к сведению.
const Circle = () => {
const [position, setPosition] = React.useState({
x: 50,
y: 50,
coords: {},
});
// Use useRef to create the function once and hold a reference to it.
const handleMouseMove = React.useRef(e => {
setPosition(position => {
const xDiff = position.coords.x - e.pageX;
const yDiff = position.coords.y - e.pageY;
return {
x: position.x - xDiff,
y: position.y - yDiff,
coords: {
x: e.pageX,
y: e.pageY,
},
};
});
});
const handleMouseDown = e => {
// Save the values of pageX and pageY and use it within setPosition.
const pageX = e.pageX;
const pageY = e.pageY;
setPosition(position => Object.assign({}, position, {
coords: {
x: pageX,
y: pageY,
},
}));
document.addEventListener('mousemove', handleMouseMove.current);
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove.current);
// Use Object.assign to do a shallow merge so as not to
// totally overwrite the other values in state.
setPosition(position =>
Object.assign({}, position, {
coords: {},
})
);
};
return (
<circle
cx={position.x}
cy={position.y}
r={25}
fill="black"
stroke="black"
strokeWidth="1"
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
/>
);
};
const App = () => {
return (
<svg
style={{
border: '1px solid green',
height: '200px',
width: '100%',
}}
>
<Circle />
</svg>
);
};
ReactDOM.render(<App />, document.querySelector('#app'));
<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>