#javascript #reactjs #canvas
Вопрос:
У меня очень странная проблема, когда моя логическая переменная useState (allowClickUpload) имеет значение True, когда я уже установил для нее значение False. Возможно, вы думаете, что я получаю доступ к нему слишком рано, и это асинхронная операция, но я могу подождать до 10 полных минут, а затем запустить журнал консоли, и это всегда верно.
В приведенном ниже коде я регистрирую любые изменения в консоли, чтобы разрешить загрузку с помощью эффекта использования. Результат такой, как и ожидалось… Сначала верно, потому что это мое начальное значение, а затем, как только я загружаю файл, оно становится ложным. Пока все хорошо. Затем я еще раз нажимаю на экран и выводю значение, и это правда, без срабатывания эффекта использования. Как это возможно? Как будто существует более 1 экземпляра или что-то в этом роде. Я действительно понятия не имею.
Пожалуйста, ознакомьтесь с приведенным ниже кодом. Это область холста, которая позволяет пользователям перетаскивать на нее изображение. Я удалил весь «лишний» код, чтобы оставить только голую реализацию, которая все еще вызывает проблему. Любая помощь будет признательна.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
const CanvasPanel = () => {
const [allowClickUpload, setAllowClickUpload] = useState(true);
const [img, setImg] = useState(document.createElement("img"));
const [data, setData] = useState(null);
const [context, setContext] = useState({});
const [canvas, setCanvas] = useState({});
useEffect(() => {
// LOG every change to allowClickUpload
console.log("allowClickUpload " allowClickUpload);
}, [allowClickUpload]);
const clearCanvas = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
};
const centerImage = () => {
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var imgWidth = img.width;
var imgHeight = img.height;
var left, top;
if (imgWidth >= windowWidth) {
left = 0;
} else {
left = (windowWidth - imgWidth) / 2;
}
if (imgHeight >= windowHeight) {
top = 0;
} else {
top = (windowHeight - imgHeight) / 2;
}
context.drawImage(img, left, top, imgWidth, imgHeight);
setData(context.getImageData(left, top, imgWidth, imgHeight).data);
};
const processDrop = (file) => {
if (file.type === "image/png" || file.type === "image/jpeg") {
var reader = new FileReader();
reader.onload = function (evt) {
img.src = evt.target.resu<
};
reader.readAsDataURL(file);
setAllowClickUpload(false);
}
};
const handleStopEvent = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer.files amp;amp; e.dataTransfer.files.length > 0) {
processDrop(e.dataTransfer.files[0]);
e.dataTransfer.clearData();
}
};
const handleImageLoad = () => {
clearCanvas();
centerImage();
};
const handlePickFile = (e) => {
processDrop(e.target.files[0]);
};
const handleMouseDown = () => {
// allowClickUpload is always True here even though the useEffect shows it is false
console.log("allowClickUpload " allowClickUpload);
if (allowClickUpload) {
document.getElementById("fileUpload").click();
}
};
useEffect(() => {
var canvas = document.getElementById("draganddrop-wrapper");
const { width, height } = canvas.getBoundingClientRect();
canvas.width = width;
canvas.height = height;
canvas.addEventListener("dragenter", handleStopEvent);
canvas.addEventListener("dragleave", handleStopEvent);
canvas.addEventListener("dragover", handleStopEvent);
canvas.addEventListener("drop", handleDrop);
canvas.addEventListener("mousedown", handleMouseDown);
window.addEventListener("drop", handleStopEvent);
window.addEventListener("dragover", handleStopEvent);
img.addEventListener("load", handleImageLoad);
setCanvas(canvas);
setContext(canvas.getContext("2d"));
return () => {
canvas.removeEventListener("dragenter", handleStopEvent);
canvas.removeEventListener("dragleave", handleStopEvent);
canvas.removeEventListener("dragover", handleStopEvent);
canvas.removeEventListener("drop", handleDrop);
canvas.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("drop", handleStopEvent);
window.removeEventListener("dragover", handleStopEvent);
img.removeEventListener("load", handleImageLoad);
};
}, [context]);
return (
<Wrapper>
<canvas id="draganddrop-wrapper"></canvas>
<input
type="file"
className="hidden"
id="fileUpload"
onChange={(e) => handlePickFile(e)}
/>
</Wrapper>
);
};
const Wrapper = styled.div`
height: calc(100vh - 160px);
flex: 1;
background: var(--neutral-color);
border: 1px solid var(--dark-text-color);
color: var(--dark-text-color);
margin: 0;
canvas {
width: calc(100vw - 310px);
height: calc(100vh - 160px);
image-rendering: pixelated;
}
.hidden {
display: none;
}
`;
export default CanvasPanel;
Вот вывод с моей консоли при запуске. Первый вывод-результат использования, показывающий значение true при загрузке. Далее-ложь, потому что я на самом деле устанавливаю ее в значение false. Далее верно, что исходит от handleMouseDown(). Также комментарии в коде.
Ответ №1:
Вы добавили контекст только в качестве зависимости к своему крючку useEffect, который настраивает прослушиватели событий. Несмотря на то, что ваш компонент выполняет повторную передачу и функция обновляется, прослушиватель событий все еще ссылается на исходную функцию, в которой ваше состояние было истинным.
Комментарии:
1. Это хороший момент, могу ли я добавить прослушиватели событий в качестве зависимостей? Просто сказать, что это звучит странно, но я не уверен, какие еще зависимости нужно добавить, чтобы события работали с правильными данными.
2. Вам нужно будет добавить функции, от которых зависят эти прослушиватели событий, в массив зависимостей. Но я согласен с тем, что написал @srosser2, это определенно лучший подход для добавления непосредственно в элемент, а не с помощью крючка useEffect.
Ответ №2:
Похоже context
, что состояние не может должным образом обновляться, так как оно инициализируется как пустой объект, и useEffect
единственные обновления об изменениях context
и единственное место setContext
, которое используется , находятся в useEffect
, поэтому оно никогда не запускается.
Кстати, в React вы обычно добавляете прослушиватели событий непосредственно в элемент (используя onClick
или onDragEnter
и т. Д.), А не используете addEventListener
и получаете элементы по идентификатору, например:
<canvas
onDrop={e => handleDrop(e)}
onDragOver={e => handleDragOver(e)}
onDragEnter={e => handleDragEnter(e)}
onDragLeave={e => handleDragLeave(e)}>
</canvas>
Взгляните на эту статью: https://www.smashingmagazine.com/2020/02/html-drag-drop-api-react/
Комментарии:
1. Хорошее замечание о событиях. Я собрал часть этого кода из более старого проекта Vue, а остальное-из простого компонента перетаскивания, который я написал для чего-то другого. Какие еще зависимости я бы добавил, чтобы это работало должным образом? Даже прочитав ваш комментарий, я не уверен. <— рубрике сейчас читать, что ссылка, которая может ответить на мой вопрос.
2. Ха — Перемещение всех моих событий на холсте решило проблему. Ваше здоровье!
3. Из того, что я могу сказать, вы устанавливаете «контекст» только с помощью setContext в эффекте использования. В эффекте использования у вас есть [контекст] в конце, и это то, что React прослушивает изменения. Поскольку вы запускаете эффект использования только при изменении «контекста» и устанавливаете контекст только в эффекте использования, вы не можете должным образом обновить состояние.