SolidJS: сообщения «вычисления, созданные вне createRoot или render, никогда не будут удалены» в журнале консоли

При работе над проектом SolidJS вы можете увидеть следующее предупреждающее сообщение в консоли JS:

computations created outside a `createRoot` or `render` will never be disposed

Некоторая информация по этому поводу доступна в выпусках репозитория SolidJS на Github. Но после их прочтения я все еще не совсем понимал, о чем идет речь и действительно ли мой код делает что-то не так.

Мне удалось отследить, откуда это взялось, и найти исправление на основе документации. Итак, я предоставляю объяснение и решение для тех, кто ищет это предупреждающее сообщение в Google.

2 ответа

По сути, это предупреждение о возможности утечки памяти из-за реактивного вычисления, созданного без надлежащего контекста, который избавился бы от него, когда он больше не нужен.

Надлежащий контекст создается несколькими способами. Вот те, о которых я знаю:

  • С помощью функции.
  • С помощью createRootфункция. Под капотом используется вот это.
  • С помощью createContextфункция.

Первый, безусловно, самый распространенный способ, потому что каждое приложение имеет по крайней мере один renderвызов функции, чтобы запустить все шоу.

Так что же делает код «вырванным из контекста»?

Вероятно, наиболее распространенным способом являются асинхронные вызовы. Создание контекста с его деревом зависимостей происходит только после завершения выполнения синхронной части кода. Сюда входят все export defaultфункция в ваших модулях и основная функция приложения.

Но код, который запускается позже из-за asyncфункция будет вне этого контекста, и любые созданные реактивные вычисления не будут отслеживаться и могут оставаться без сборки мусора.

Пример

Допустим, у вас есть экран ввода данных и Saveкнопку на нем, которая делает вызов API на ваш сервер для сохранения данных. И вы хотите сообщить пользователю, была ли операция успешной или нет, с помощью красивого сообщения в формате HTML.

      [msg,setMsg] = createSignal(<></>)

async function saveForm(){
  ...
  setMsg(<p>Saving your data.<i>Please stand by...</i></p>)
  const result=await callApi('updateUser',formData)
  if(result.ok){
    setMsg(<p>Your changes were <b>successfully</b> saved!</p> )
  } else {
    setMsg(<p>There was a problem saving your data! <br>Error: </p><pre>{result.error}</pre> )
  }
} 

...
  <div>
    ...
    <button onClick={saveForm} >Save</button>
    {msg()}
  </div>

Это приведет к появлению вышеупомянутого предупреждения, когда вызов API возвращает ошибку, но не в других случаях. Почему?

Причина этого в том, что SolidJS считает вставки кода внутри JSX реактивными, т. е. требующими наблюдения и переоценки. Таким образом, вставка сообщения об ошибке из вызова API создает реактивное вычисление.

Решение

Я нашел решение в самом конце документа SolidJS. Это специальный модификатор JSX:

Его можно использовать в начале выражения с фигурными скобками, и он указывает компилятору SolidJS явно не делать это выражение реактивным. Другими словами: он будет оцениваться один и только один раз, когда узлы DOM создаются из JSX.

В приведенном выше примере вот как это использовать:

setMsg(<p>There was a problem saving your data! <br>Error: </p><pre>{ result.error}</pre> )

После этого предупреждающих сообщений больше не будет :)


В моем случае у меня был ввод, и когда этот ввод изменился, я воссоздал рисунок SVG. Поскольку создание SVG было дорогостоящей операцией, я добавил устранение дребезга в createEffectфункция, которая запускалась при изменении ввода. debounce- это метод отсрочки обработки до тех пор, пока входные данные не перестанут изменяться в течение как минимум X времени. Это включало запуск кода генерации SVG внутри setTimeoutфункции, то есть вне основного контекста. С использованием /*@once*/модификатор везде, где я вставил выражение в сгенерированном JSX, устранил проблему.

Ошибка «Вычисления, созданные вне корня» возникает, когда вы выполняете вычисление вне области отслеживания.

Что такое вычисление? Любая форма эффекта, которая может подписываться на сигнал, включая те, которые создаются с помощью,,,ифункции. Твердые компоненты также являются эффектами.

Что такое область отслеживания? Область отслеживания — это область JavaScript, имеющая доступ к владельцу. Еслифункция возвращает значение, вы находитесь внутри области отслеживания. Существует несколько способов создания области отслеживания, но самый простой из них — , другим нравится иливызывает его внутри себя.

Зачем нам нужна область отслеживания? Для управления памятью. Область отслеживания отслеживает зависимости эффекта. Подумайте о компоненте: компонент может создавать элемент DOM, и у него есть дочерние компоненты, которые могут создавать другие элементы DOM. Не только компоненты, но даже обычные эффекты могут содержать другие эффекты внутри его тела.

Если эффект ожидает сигнала, он запустится повторно. Когда они снова забегут, они повторят все, что сделали. Если он создает компонент, он создаст новый компонент. Эффект, содержащий другие эффекты, которые содержат другие эффекты, может потреблять большое количество ресурсов. Если их потреблением не управлять, оно быстро выйдет из-под контроля.

Когда эффект создается в области отслеживания, Solid назначает для него владельца и строит график, показывающий, кто кем владеет. Всякий раз, когда владелец выходит за пределы области действия, любые вычисления, принадлежащие этому владельцу, удаляются.

Область отслеживания отслеживает внутренние ресурсы, ресурсы, созданные самим SolidJS. Для внешних ресурсов, таких как соединение сокетов, вам необходимо освободить их вручную черезкрючки.

Эффект может иметь доступ к сигналу или нет, не имеет значения. Это отслеживание зависимостей существует вне сигнала. Попробуйте запустить любой эффект, не имеющий доступа к сигналу, вы все равно получите ошибку:

      import { createEffect, createSignal } from 'solid-js';
createEffect(() => console.log('Hello World'));

Вы получите эту ошибку, если выполните эффект внутри асинхронной функции, даже если асинхронная функция находится в области отслеживания. Почему? Потому что Solid запускается синхронно. Это работает циклично. Эффекты подписываются на сигнал, когда реагируют на его значение, и отписываются после обратного вызова. Итак, в каждом цикле обновления все строится и разрушается. При запуске асинхронной функции владелец предыдущего цикла будет давно удален. Таким образом, эффект, который находится внутри асинхронной функции, будет отделен от графа зависимостей и станет недействительным. Но решение простое: предоставить нового владельца, обернув эффектфункция:

      runWithOwner(outerOwner, () => {
  createEffect(() => {
    console.log('Hello World');
  });
})

В других случаях, когда у вас нет корневой области, лучше использоватьилифункции.

Теперь пришло время объяснить, как прагма решает проблему внутри принятого ответа:

Прежде всего, вы создаете компонент внутри функции обратного вызова, вызывая.

Прагма помечает значение свойства как статическое.

Возьмите этот компонент:

      <Comp count={count()} />

ОБЫЧНО, свойство count компилируется в функцию получения, которая возвращает значение:

      _$insert(_el$3, _$createComponent(Comp, {
  get count() {
    return count();
  }
}));

Это необходимо для сохранения реактивности при передаче значений от родителя к дочернему элементу.

При добавлении значение свойства будет рассматриваться как статическое значение:

      _$insert(_el$3, _$createComponent(Comp, {
  count: count()
}));

Помните, мы говорили, что компоненты — это эффекты. КогдаПри использовании Solid рассматривает дочерние элементы как статические значения, а не как компоненты. Другими словами, Solid не видит никакого эффекта внутри асинхронной функции, а только вызов функции, который возвращает статическое значение:

      <pre>{/*@once*/ result.error}</pre>

Кстати, пример кода, который используется внутри принятого ответа, не является идиоматическим компонентом Solid. Лучше не смешивать пользовательский интерфейс и состояние таким образом.

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