UseRef.current существует, но имеет значение null в зависимости от области действия

Я пытаюсь нарисовать изображение на холсте, используя React и Fabric.js.
Демо (CodeSandBox)

В приведенной выше демонстрации при нажатии кнопки «Нарисовать изображение» можно было бы ожидать, что изображение будет немедленно нарисовано на холсте, но изображение на холсте не рисуется.

UseRef.current существует, но имеет значение null в зависимости от области действия

Причина, по которой изображение не рисуется, по-видимому, заключается в том, что ток useRef равен нулю при выполнении функции рисования изображения на холсте. (Если условие isInput в строке 50 удалено, containerRef.current больше не равно нулю, и изображение может рисовать на холсте.)

Не могли бы вы помочь мне нарисовать изображение на холсте?


Код выглядит следующим образом.

const App = () => {
  const [img, setImg] = useState(defaultImg);
  const changeImg = ({ target: { value } }: { target: { value: string } }) =>
    setImg(value);

  const [isInput, setIsInput] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  const checkCurrent = (caller: string) => {
    console.info(`[${caller}]Container: ${containerRef.current}`);
  };
  useEffect(() => {
    if (!isInput) return;

    checkCurrent("useEffect");
  }, [isInput]);

  const drawImg = (e: { preventDefault: () => void }) => {
    e.preventDefault();
    if (!img) return;
    setIsInput(true);

    checkCurrent("drawImg");

    canvas = new fabric.Canvas("canvas");
    fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
      canvas.add(imgObj).renderAll();
    });
  };

  return (
    <div className = "App">
      {!isInput && (
        <form onSubmit = {drawImg}>
          Demo image url
          <input type = "text" onChange = {changeImg} defaultValue = {img} />
          <button type = "submit">Draw image</button>
        </form>
      )}

      {isInput && (
        <>
          <button onClick = {() => setIsInput(false)}>Reset</button>
          <div ref = {containerRef} className = "canvas_container">
            <canvas ref = {canvasRef} id = "canvas" />
          </div>
        </>
      )}
    </div>
  );
};

export default App;

Я понимаю, что containerRef.current равно нулю, если нет DOM.

Однако,

  1. div, которому установлен containerRef, рисуется setIsInput(true)
  2. containerRef.current полученное внутри useEffect не равно нулю.

Исходя из вышеизложенного, мы можем ожидать, что containerRef.current не равно нулю даже внутри функции drawImg.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
17
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вызов setIsInput(true) просто запрашивает ререндеринг компонента. Это произойдет в ближайшее время, но обычно нет происходит немедленно и синхронно. Таким образом, ваш код в строке после setIsInput(true) не может предполагать, что ссылка уже обновлена.

Обычно вы делаете это в два этапа. Во-первых, перерисуйте компонент, во-вторых, нарисуйте изображение на холсте. Это можно сделать, поместив код рисования в useEffect, поскольку, как вы заметили, это происходит после заполнения ссылки (потому что это происходит после того, как рендеринг был сброшен в дом):

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;
  setIsInput(true);
}

useEffect(() => {
  if (!isInput) return;

  const canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
}, [isInput]);

Поскольку вы используете реакцию 18, следует упомянуть еще одну опцию, хотя я рекомендую вам не использовать ее, если она вам действительно не нужна. React 18 добавляет новую функцию под названием flushSync, которая может принудительно отображать обновления состояния синхронно.

import { flushSync } from 'react-dom'; // Note: 'react-dom', not 'react'

// ...

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;

  flushSync(() => {
    setIsInput(true);
  });

  canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
};

Обязательно ознакомьтесь с примечаниями в документация по флеш-синхронизации, потому что возможны последствия, если вы решите его использовать.

Другие вопросы по теме

Ошибка, связанная с props и renderItem в реагирующем собственном плоском списке
Создание массива в пользовательском хуке перерисовывает значение при каждом изменении состояния
Добавьте маркер Mapbox в событие двойного щелчка с помощью React
Как передавать разные реквизиты при каждом щелчке между элементами одного уровня в React? (он же «Подъем состояния»)
Следующий js, ReactDom.render больше не поддерживается
Обновление сборника рассказов с 6.3.7 до 6.4.22 показывает счетчик и отсутствие историй в реакции/машинописи
Я пытаюсь нарисовать круги и добавить всплывающее окно на карту, но постоянно получаю ошибки. Не удается прочитать свойства неопределенного (чтение «lng»)
Передача реквизита из одного компонента в другой в reactjs, но возвращает undefined
Экспо зависает при использовании таблицы стилей ReactNative
Как я могу проверить, что ввод React не является пустым и действительным адресом электронной почты?