Как во vue.js сделать быструю фильтрацию списка без лишних просчётов?
Привет! Помогите, пожалуйста. Не выходит сообразить что можно сделать с этим. Разве что написать собственный рендеринг в обход vue.
Суть в том, что я делаю чат с плавным скроллингом (но не в этом суть). Для увеличения производительности я считаю размер и координаты каждого сообщения. Дальше с помощью выч. свойства фильтрую те сообщения, которые на данный момент попадают в окно чата и с помощью v-for вывожу лишь их. Проблема лишь в том, что когда происходит смещение и выводятся нужные сообщения - заново происходит рендеринг всех отображаемых сообщений (хочу заметить то что сообщения имеют довольно тяжёлую структуру и рендерить их по несколько раз довольно накладно во время скроллинга, при том, что результат рендеринга всегда одинаковый). На то чтобы отрендерить примерно 10 текущих сообщений уходит (на мощном компьютере) около 1мс и ещё 3мс уходит на то чтобы сравнить этот и старый списки (ХОТЯ сравнивать в принципе нечего, я и так уже точно знаю, что удалились или добавились определённые элементы, а все остальные остались без изменения, но vue всё равно тратит на сравнение старого и нового списка непомерно много времени), что в условиях 16мс на один кадр ну совсем не комильфо, хотя по сути сообщения все одни и те же, а все изменения заключаются лишь в добавление и удаление элементов списка. Так же хочу заметить что есть проблема, что на каждое сообщение вешается 2 обработчика, что тоже вызывает определённые сложности.
Вот думаю сейчас попробовать рендерить сообщения заранее без помощи vue и сохранять в виде строк, а дальше просто выводить с помощью v-html, хотя в таком случае не очень понятно что делать с обработчиками.
Ещё один вариант - написать вообще полностью свой рендеринг для сообщений и просто временно удалять из DOM ненужные элементы, а потом восстанавливать их.
Но всё же хотелось бы решить проблему более штатскими средствами. Было бы идеально если бы я мог сам реализовать функцию обновления DOM дерева для данного списка... Всё что по сути нужно, так это один раз отрендерить весь список, а дальше просто скрывать и добавлять обратно нужные элементы (логика очень простая и не требует глубокого сравнения DOM деревьев...)
А вы уверены, что ваш способ пересчета прибавляет производительности? Может просто сделать какой-то буфер 20-50 сообщений, которые бы один раз отрендерились, и если юзер скролит, и буфера не хватает подгрузить еще. И ключи к элементам вы везде проставляете? Потому как vue достаточно оптимизирован в плане того, чтобы не делать лишнего рендеринга.
Evgeny Kulakov, к сожалению vue даже при наличие ключей всё равно рендерит список заново при каждом просчёте. Он экономит ресурсы отрисовщика браузера, но при этом расходует слишком много ресурсов самого js, выполняя абсолютно бесполезные вычисления.
Evgeny Kulakov, ну это было бы классно, но на практике в виртуальном DOM рендерится новый список, дальше очень долго сверяется со старым и дальше уже вносятся изменения в настоящий DOM. Vue тратит очень много времени на то чтобы понять, что элементы не изменились, а лишь добавились новые.
LordGuard, насчет рендеринга нового списка не очень понял. У вас в виртуал дом создались элементы первый раз, потом произошел их рендеринг, потом, по идее, когда список меняется, виртуал дом перебирает элементы, смотрит сначала ключ, если такой уже есть, то он уже не будет ничего создавать, он посмотрит на входящие свойства и если они изменились, то обновит соответствующий элемент и только в случае если ключа нет, он создаст новый элемент. Может у у вас ключи меняются или объекты сами как-то меняются, когда вы фильтрованный список получаете.
Сегодня столкнулся с проблемой, что vue не отрисовывает дочерние компоненты, если к объекту добавлять дочерний объект этого же объекта. Пришлось ковырять костыль с клонированием объекта для правильной отрисовки дерева в моем случае. Возможно потому что использовал vuex
Evgeny Kulakov, нет, точно ничего не меняется, список абсолютно статичный, на его основе сделано выч. свойство с самым обычным фильтром. В результате даже добавление всего 1 элемента в список приводит к вычислениям на 2мс+
LordGuard, список он все-таки не рендерит полностью заново, в этом легко убедиться если посмотреть сколько по времени первоначальная отрисовка занимает или написать что-то типа :key="`${Math.random(100000000)}`" чтобы ключи рандомные генерировались. А так да, при таком количестве элементов процедура сверки всего виртуалдом дерева отнимает время. Нужно наверное придумывать решение при котором в виртуал доме будет как можно меньшее кол-во элементов или писать свой рендерер, заточенный под вашу задачу.
Evgeny Kulakov, ты ошибаешься. Он полностью ререндерит список в виртуал дом. Именно полностью. При первом запуске лишь добавление в реальный DOM (обновление) происходит дольше. Очевидно почему - во vue.js как никак но всё же оптимизирован алгоритм обновления реального DOM, а вот рендер (функция vue._render) и в том и в другом случае занимает около 30мс.
Можешь сам это проверить с помощью отладчика chrome. Твоя ошибка в том, что ты сравнил всё вместе (рендер в virtual DOM, сравнение, отрисовку браузером), а я говорил именно о рендере в virtual DOM.
Evgeny Kulakov, угу, но ведь при большом списке и рендер в виртуальный DOM занимает не мало времени, хотя vue мог бы более умно действовать, сравнивая сначала именно сами массивы данных. Ну или хотя бы предоставить инструменты для информирования его о том, что произошло лишь (например) добавление n элементов без изменения других. Или скажем дать возможность показать ему, что элементы с такого-то по такой не изменились или что-то в этом духе...
Бегло просмотрел комменты и не увидел предложения использовать директиву v-once.
Однократно рендерит элемент или компонент. При повторном рендеринге он, а также все его потомки, рассматриваются как статический контент и пропускаются. Это поможет увеличить производительность обновлений. https://ru.vuejs.org/v2/api/#v-once