Я не специалист по Метеору. Мое понимание работы Meteor все еще несколько поверхностно (или, как, вероятно, выразился бы исследователь компьютерных наук Юха Сорва: моя ментальная модель воображаемой машины Meteor не очень стойкий). Тем не менее, я решил поделиться этим опытом, потому что мне потребовалось некоторое время, чтобы исправить эту проблему в моем коде, и другие могут столкнуться с той же проблемой в какой-то момент.

Проблема

Я разработал Информу, систему асинхронного обучения смешанному мастерству, которую я использую для преподавания своих курсов в Университете Лугано. Informa основана на Meteor и использует Iron Router. Одна из моих страниц позволяет инструкторам редактировать тест. На странице отображается информация о викторине и кнопки для добавления и удаления задач. Эти кнопки вызывают обновление теста (путем вызова методов Meteor, которые изменяют тест на сервере). Тест — это документ, хранящийся в моей коллекции Quizzes, и кнопки обновляют одно из его полей (массив идентификаторов задач).

Учитывая реактивность Meteor, различные элементы на странице автоматически обновляются всякий раз, когда нажимается такая кнопка (проблема добавляется или удаляется). Это именно то, что я хотел. Однако, и это то, чего я не хотел, при каждом изменении теста страница прокручивается вверх, вместо того, чтобы оставаться в текущей позиции прокрутки.

Отладка

Этот прыжок в начало страницы действительно раздражает. Пользователю всегда приходится прокручивать вниз до того места на странице, где он был. Поэтому я попытался выяснить, что вызвало этот скачок. Сначала я подумал, что это какой-то уродливый код в одном из моих обработчиков событий, или, может быть, какой-то помощник шаблона или какой-то код в обработанном обратном вызове шаблона, или aslagle:reactive-table, который я использую на этой странице, или устаревший пакет okgrow:iron-router-autoscroll.

Однако затем я прочитал комментарий Тома Коулмана по поводу проблемы изменения контекста данных переходят наверх для iron-router:

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

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

Я посмотрел документацию по железному маршрутизатору и нашел такое утверждение о реактивности:

Ваши функции маршрута и большинство хуков запускаются в реактивном вычислении. Это означает, что они будут запускаться автоматически при изменении реактивного источника данных. Например, если вы вызываете Meteor.user() внутри вашей функции маршрута, ваша функция маршрута будет перезапускаться каждый раз при изменении значения Meteor.user().

Теперь это был тот намек, который мне был нужен. Это не имело отношения к конкретному примеру вызова Meteor.user(). Но именно эта реактивность меня и укусила. Это кажется такой общей ловушкой, что я решил поделиться ею здесь.

Ошибка

Несколько упрощая, код рассматриваемого маршрута определялся следующим образом:

Router.route(‘/course/:courseId/quiz/:quizId/edit’, function() {
 this.layout(‘sidebarLayout’);
 var course = Courses.findOne({_id: this.params.courseId});
 var quiz = Quizzes.findOne({_id: this.params.quizId});
 this.render(‘quizEditPage’, {
   data: function() {
     return {
       course: course,
       quiz: quiz,
     };
   }
 });

Учитывая URL-адрес, указывающий на викторину в курсе, функция маршрутизации находит курс и викторину в соответствующих коллекциях. Затем он вызывает метод render() для отображения шаблона «quizEditPage», передавая ему функцию, создающую контекст данных (объект, содержащий найденный курс и викторину).

Исправление

Следующее небольшое изменение в функции приводит к исчезновению прокрутки:

Router.route(‘/course/:courseId/quiz/:quizId/edit’, function() {
 this.layout(‘sidebarLayout’);
 var course = Courses.findOne({_id: this.params.courseId});
 this.render(‘quizEditPage’, {
   data: function() {
     var quiz = Quizzes.findOne({_id: this.params.quizId});
     return {
       course: course,
       quiz: quiz,
     };
   }
 });

Почему пропадает прокрутка? Версия с ошибками выполняла запрос к базе данных в функции, которую я передал в route(). Фиксированная версия выполняет запрос к базе данных в функции, создающей контекст данных.

Функция, переданная в route(), выполняется в реактивном вычислении, поэтому всякий раз, когда что-то изменяется (например, объект викторины, который он находит в базе данных), вычисление выполняется повторно. А это значит, что он снова вызывает render(), который перерисовывает весь шаблон.

Обсуждение

Мой код содержит различные функции маршрутизации, которые демонстрируют эту ошибку. Даже описанная выше «фиксированная» функция по-прежнему будет вызывать повторную визуализацию, если объект курса изменится (хотя шаблон «quizEditPage» не вызовет таких изменений в курсе). Первоначально я вытащил эти запросы в окружающую функцию маршрутизации, потому что я использовал результат в нескольких разных вызовах для рендеринга. Я не особо задумывался о моменте времени, в котором они фактически выполняются (время маршрутизации), и о том, что для правильного выполнения своей работы функция маршрутизации должна будет повторно выполняться реактивно, а это означает, что она будет повторно отображать весь шаблон каждый раз, когда извлекаемые данные изменялись.

Реактивные вычисления в Meteor действительно мощные. Но я считаю, что мне все еще нужно немного поработать над своей ментальной моделью, чтобы избежать таких ловушек в будущем.