Vue 3.5.0-alpha.3: появился
#vue_3_5 #changelog
Позволяет описывать свойства для
- Семантически понятнее - явно определяется как переменная для template ref
-
- Значение атрибута
- Возможно, будет особая поддержка в IDE ?
- Commit: https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df
useTemplateRef(key)
для Template Ref
#vue_3_5 #changelog
Позволяет описывать свойства для
Template Ref
аналогично ref()
. Отличия:- Семантически понятнее - явно определяется как переменная для template ref
-
readonly
- нельзя случайно изменить вручную- Значение атрибута
ref
- определяемый отдельно ключ, а не имя переменной (свойства компонента)- Возможно, будет особая поддержка в IDE ?
<script setup lang="ts">
// Vue 3.5+
const inputElement = useTemplateRef<HTMLInputElement>('inputElement')
// Или с отдельным ключом
const fileInputKey = 'FILE_INPUT'
const fileInputElement = useTemplateRef<HTMLInputElement>(fileInputKey)
// Аналог в прошлых версиях
const inputElement = ref<HTMLInputElement|null>()
</script>
<template>
<input ref="inputElement" />
<input :ref="fileInputKey" type="file" />
</template>
- Commit: https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df
GitHub
feat: useTemplateRef() · vuejs/core@3ba70e4
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web. - feat: useTemplateRef() · vuejs/core@3ba70e4
👍8🤔8🔥5
Vue 3.5: появился отдельный API для работы с cleanup-функциями в watch/watchEffect -
- позволяет зарегистрировать функцию “очищения”, которая будет вызываться прямо перед повторным запуском watch/watchEffect (например, если изменилась зависимость и он вот-вот сработает);
- подходит для того, чтобы, например, отменять асинхронные запросы (избегая гонки состояний) или очищать старые таймеры перед запуском новых;
- сильно облегчает работу в паре с
- имеет те же ограничения, что и остальные функции, привязанные к контексту выполнения - должен выполняться синхронно и вызываться внутри watch/watchEffect;
- дополнительно имеет второй аргумент
либо можно воспользоваться еще одной новой функцией
тогда cleanup-функция будет зарегистрирована только в том случае, если мы находимся внутри watch/watchEffect.
Использование:
Новый подраздел в документации: https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup
PR: https://github.com/vuejs/core/pull/9927
#vue_3_5 #changelog
onWatcherCleanup
:- позволяет зарегистрировать функцию “очищения”, которая будет вызываться прямо перед повторным запуском watch/watchEffect (например, если изменилась зависимость и он вот-вот сработает);
- подходит для того, чтобы, например, отменять асинхронные запросы (избегая гонки состояний) или очищать старые таймеры перед запуском новых;
- сильно облегчает работу в паре с
watch
: сейчас функция onCleanup
передается в него третьим аргументом (после value и oldValue), что довольно неудобно, особенно, если первые 2 аргумента не нужны, поэтому раньше приходилось писать (_, __, onCleanup)
;- имеет те же ограничения, что и остальные функции, привязанные к контексту выполнения - должен выполняться синхронно и вызываться внутри watch/watchEffect;
- дополнительно имеет второй аргумент
failSilently
, куда можно передать true
, чтобы не получать предупреждение в консоли, если функция вызвана за пределами watch/watchEffect, что открывает окно для следующих паттернов:
export function request() {
const controller = new AbortController();
// регистрируем cleanup-функцию и передаем failSilently: true
onWatcherCleanup(() => controller.abort(), true);
return fetch(…, { signal: controller.signal });
}
либо можно воспользоваться еще одной новой функцией
getCurrentWatcher
и переписать код выше иначе:
export function request() {
const controller = new AbortController();
// если мы внутри вотчера
if (getCurrentWatcher()) {
// то регистрирурем cleanup-функцию
onWatcherCleanup(() => controller.abort());
}
return fetch(…, { signal: controller.signal });
}
тогда cleanup-функция будет зарегистрирована только в том случае, если мы находимся внутри watch/watchEffect.
Использование:
watchEffect(async () => {
const response = await request(…);
});
Новый подраздел в документации: https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup
PR: https://github.com/vuejs/core/pull/9927
#vue_3_5 #changelog
vuejs.org
Vue.js
Vue.js - The Progressive JavaScript Framework
🔥15🤔2👍1
Работа с картинками в шаблоне
1. Почему такой код не работает? Картинка точно есть по этому, и без переменной работает!
Собранное приложение в проде имеет другие файлы по другим путям. Путь относительно исходников - некорректный. Чтобы это работало, нужно работать с картинками и другими ресурсами, как с модулями, прогоняя их через сборщик. Сборщик переместит файл, поменяет имя, и вернёт новое имя, а может даже вставит inline-ссылкой (data url).
2. Но у меня раньше работало без импорта! Например, так:
Сборщик с
Только чистый
3. Что же тогда делать, если надо хранить путь в данных?
Можно использовать:
- Явные импорты, если заранее известен набор файлов
-
-
-
4. Но как работают все эти функции, если тут путь тоже вычисляется только в рантайме, а нужен ещё на этапе сборки?
Сборщик забирает шаблонную строку или конкатенацию строк. Затем считает, что вы можете импортировать в рантайме ЛЮБОЙ файл, подходящий под шаблон. В примерах выше сборщик найдёт все файлы
Более сложные JS выражения не будут работать.
Подробности:
- https://vitejs.dev/guide/assets.html
- https://vitejs.dev/guide/features.html#glob-import
- https://webpack.js.org/guides/asset-management/#loading-images
- https://github.com/vitejs/vite/issues/10597
!src
#help1. Почему такой код не работает? Картинка точно есть по этому, и без переменной работает!
<img :src="`../assets/images/${image}`" />
Собранное приложение в проде имеет другие файлы по другим путям. Путь относительно исходников - некорректный. Чтобы это работало, нужно работать с картинками и другими ресурсами, как с модулями, прогоняя их через сборщик. Сборщик переместит файл, поменяет имя, и вернёт новое имя, а может даже вставит inline-ссылкой (data url).
import Cat from '../images/Cat.png'
console.log(Cat) // выведет /img/Cat.45df63.png
<img :src="Cat" />
2. Но у меня раньше работало без импорта! Например, так:
<img src="@/assets/images/cat.png" />
Сборщик с
@vue/sfc-compiler
знает, что в src
в HTML и url()
в CSS указывается путь. Если он относительный (не с /), то он автоматически работает с ним, как с модулем, сам делая импорт. Но это должен быть статический путь именно в таком атрибуте. :src
с привязкой у JS вычислению или даже data-src
— работать не будут.<img :src="`@/assets/images/cat.png`" data-src="@/assets/images/cat.png" />
<img src="@/assets/images/dog.png" data-src="@/assets/images/dog.png" />
Превращается в
<img src="@/assets/cat.653gtd.png" data-src="@/assets/images/cat.png" />
<img src="/assets/dog.45df63.png" data-src="@/assets/images/dog.png" />
Только чистый
src
обработался, как путь до модуля.3. Что же тогда делать, если надо хранить путь в данных?
Можно использовать:
- Явные импорты, если заранее известен набор файлов
-
require
в Webpack-
new URL
в Vite (без поддержки алиасов и SSR)-
import.glob
в Vite, если нужен список всех файлов// Нам нужна картинка питомца из переменной
const myPet = 'dog'
// Webpack + Vite - прямой импорт
import cat from '@/assets/pets/cat.png'
import dog from '@/assets/pets/dog.png'
const pets = { cat, dog }
const myPetImage = pets[myPet]
// Webpack - require
const myPetImage = require(`../assets/pets/${myPet}.png`)
// Vite - new URL
const myPetImage = new URL(`../assets/pets/${myPet}.png`, import.iss.oneta.url).href
// Vite - import.glob
const petImages = import.iss.oneta.glob('../assets/pets/*.png', { eager: true })
const myPetImage = petImages[`../assets/pets/${myPet}.png`]
4. Но как работают все эти функции, если тут путь тоже вычисляется только в рантайме, а нужен ещё на этапе сборки?
Сборщик забирает шаблонную строку или конкатенацию строк. Затем считает, что вы можете импортировать в рантайме ЛЮБОЙ файл, подходящий под шаблон. В примерах выше сборщик найдёт все файлы
.png
в директории /assets/pets/
.Более сложные JS выражения не будут работать.
new URL(getPetImageByName(myPet)) // ❌ Не сработает
Подробности:
- https://vitejs.dev/guide/assets.html
- https://vitejs.dev/guide/features.html#glob-import
- https://webpack.js.org/guides/asset-management/#loading-images
- https://github.com/vitejs/vite/issues/10597
👍8❤4🔥2👏1🤔1
Отличия ref и reactive
!ref #help
Ref:
- позволяет удобно и просто перезаписать переменную целиком:
- может быть использован с примитивами (string, number, boolean и т.д.);
- представляет из себя геттер и сеттер в случае с примитивами (без Proxy);
- в случае с объектами просто вызывает
- обязательно имеет контейнер, в котором хранит значение (.value), о котором нельзя забывать.
Reactive:
- работает только с непримитивными значениями (объекты и массивы);
- использует Proxy и глубокую реактивность по умолчанию;
- не может быть просто перезаписан целиком по аналогии с
- удобен для группировки связанных значений в общий объект (чтобы не создавать отдельные независимые переменные для связанного состояния);
- поддерживает ref unwrapping (https://vuejs.org/guide/essentials/reactivity-fundamentals.html#additional-ref-unwrapping-details), а значит в него можно положить другие ref’ы, и они раскроются (не нужно будет писать лишний .value) внутри объекта;
- иногда может быть удобнее в типизации - для него не нужно использовать отдельный тип
В общем случае можно просто использовать
Полезные ссылки:
1. https://vuejs.org/guide/essentials/reactivity-fundamentals.html
2. https://vuejs.org/guide/extras/reactivity-in-depth.html#how-reactivity-works-in-vue
3. https://stackoverflow.com/questions/61452458/ref-vs-reactive-in-vue-3/65262638#65262638
!ref #help
ref
и reactive
позволяют создать реактивную переменную, но имеют некоторые отличия:Ref:
- позволяет удобно и просто перезаписать переменную целиком:
example.value = 123
, при этом сохранив реактивность;- может быть использован с примитивами (string, number, boolean и т.д.);
- представляет из себя геттер и сеттер в случае с примитивами (без Proxy);
- в случае с объектами просто вызывает
reactive
и передает обработку ему;- обязательно имеет контейнер, в котором хранит значение (.value), о котором нельзя забывать.
Reactive:
- работает только с непримитивными значениями (объекты и массивы);
- использует Proxy и глубокую реактивность по умолчанию;
- не может быть просто перезаписан целиком по аналогии с
ref
, потому что потеряется реактивность;- удобен для группировки связанных значений в общий объект (чтобы не создавать отдельные независимые переменные для связанного состояния);
- поддерживает ref unwrapping (https://vuejs.org/guide/essentials/reactivity-fundamentals.html#additional-ref-unwrapping-details), а значит в него можно положить другие ref’ы, и они раскроются (не нужно будет писать лишний .value) внутри объекта;
- иногда может быть удобнее в типизации - для него не нужно использовать отдельный тип
MaybeRef<T>
, как в случае с обычным ref
(например, если хочется иметь объект, который может быть как реактивным, так и нет, то типизация и работа с reactive
будет в целом выглядеть удобнее).В общем случае можно просто использовать
ref
, если это кажется проще, но про reactive
тоже полезно помнить, потому что его особенности могут рано или поздно пригодиться.Полезные ссылки:
1. https://vuejs.org/guide/essentials/reactivity-fundamentals.html
2. https://vuejs.org/guide/extras/reactivity-in-depth.html#how-reactivity-works-in-vue
3. https://stackoverflow.com/questions/61452458/ref-vs-reactive-in-vue-3/65262638#65262638
🔥9👍7👏3❤2🤔1
Зачем Pinia, если можно написать свой стор?
!store #help
Во-первых, это прежде всего велосипед - мы пишем свое собственное решение, которое делает то же самое, что и Pinia.
Во-вторых, у этого велосипеда будет масса недостатков по сравнению с готовым решением:
1. Свой простенький стор не будет унифицирован - у него нет единого API, которое диктует формат определения новых сторов, описания их полей и методов. Можно описывать каждое свойство в виде
2. Если идти по пути комплексного решения, продумывать унифицированный интерфейс (аналог
3. В Pinia сторы инициализируются лениво: если в приложении описано 20 сторов, но на странице используется только один, то инициализирован будет тоже только один. В своем решении из коробки не будет “ленивости”, это отдельный функционал, который требует времени на реализацию.
4. В своем простеньком сторе не будет поддержки SSR: код, описанный в ES-модуле, выполняется на сервере только один раз (при старте сервера), а затем переиспользуется каждым клиентом. Это значит, что стор будет один общий на всех клиентов, вместо изолированных инстансов под каждого клиента. В своем сторе не будет поддержки сериализации для передачи данных с сервера на клиент.
5. Переменные, описанные в скоупе ES-модуля, являются глобальными, поэтому, если эти переменные будут использованы в любом другом коде и на них останутся ссылки, то сборщик мусора будет игнорировать такой код и никогда не освободит память, которую этот код занимает. В Pinia такой проблемы нет, потому что сторы создаются не в скоупе модуля.
6. В Pinia все сторы объединены в общий
7. В Pinia все сторы сгруппированы и хранятся в одной общей Map-структуре, в которой всегда можно найти любой стор. В своей реализации сторы будут раскиданы по модулям и ничем не объединены.
8. В Pinia есть поддержка HMR: стейт точечно обновляется при изменении кода, а не сбрасывается целиком, как было бы в своей собственной реализации.
9. В Pinia есть система плагинов: удобно подвязываться на жизненный цикл стора и, например, писать интеграции с внешними хранилищами. И для этого есть унифицированный, задокументированный и оттестированный API.
10. Pinia интегрирована во Vue DevTools: можно смотреть на таймлайны, отслеживать вызовы функций и дебажить сторы.
При этом у своего решения, как правило, практически нет никаких весомых плюсов, кроме потенциально меньшего размера. Но, как понятно из пунктов выше, за эту экономию придется платить функционалом и удобством.
!store #help
Во-первых, это прежде всего велосипед - мы пишем свое собственное решение, которое делает то же самое, что и Pinia.
Во-вторых, у этого велосипеда будет масса недостатков по сравнению с готовым решением:
1. Свой простенький стор не будет унифицирован - у него нет единого API, которое диктует формат определения новых сторов, описания их полей и методов. Можно описывать каждое свойство в виде
export const count = ref(0);
, а можно оборачивать все в useCount
. Этот формат будет негласным и его нужно проговаривать с командой, документировать и делать то, что уже сделано в Pinia.2. Если идти по пути комплексного решения, продумывать унифицированный интерфейс (аналог
defineStore
), то это будет путь к своей копии Pinia, только это решение не покрыто тестами, не прошло проверку в проде, не тестировалось на утечки памяти, не знакомо другим разработчикам, его нельзя добавить в новый проект одной командой и еще много других “не”.3. В Pinia сторы инициализируются лениво: если в приложении описано 20 сторов, но на странице используется только один, то инициализирован будет тоже только один. В своем решении из коробки не будет “ленивости”, это отдельный функционал, который требует времени на реализацию.
4. В своем простеньком сторе не будет поддержки SSR: код, описанный в ES-модуле, выполняется на сервере только один раз (при старте сервера), а затем переиспользуется каждым клиентом. Это значит, что стор будет один общий на всех клиентов, вместо изолированных инстансов под каждого клиента. В своем сторе не будет поддержки сериализации для передачи данных с сервера на клиент.
5. Переменные, описанные в скоупе ES-модуля, являются глобальными, поэтому, если эти переменные будут использованы в любом другом коде и на них останутся ссылки, то сборщик мусора будет игнорировать такой код и никогда не освободит память, которую этот код занимает. В Pinia такой проблемы нет, потому что сторы создаются не в скоупе модуля.
6. В Pinia все сторы объединены в общий
effectScope
, а значит любой стор можно одной строкой “удалить”, очистив все его ref/computed/watch/watchEffect/etc., и освободив память. В своей простой реализации так сделать будет нельзя.7. В Pinia все сторы сгруппированы и хранятся в одной общей Map-структуре, в которой всегда можно найти любой стор. В своей реализации сторы будут раскиданы по модулям и ничем не объединены.
8. В Pinia есть поддержка HMR: стейт точечно обновляется при изменении кода, а не сбрасывается целиком, как было бы в своей собственной реализации.
9. В Pinia есть система плагинов: удобно подвязываться на жизненный цикл стора и, например, писать интеграции с внешними хранилищами. И для этого есть унифицированный, задокументированный и оттестированный API.
10. Pinia интегрирована во Vue DevTools: можно смотреть на таймлайны, отслеживать вызовы функций и дебажить сторы.
При этом у своего решения, как правило, практически нет никаких весомых плюсов, кроме потенциально меньшего размера. Но, как понятно из пунктов выше, за эту экономию придется платить функционалом и удобством.
👍13🔥3❤2