Vue,js Feed — Канал русскоговорящего сообщества
174 subscribers
3 photos
30 links
Канал Vue.js русскоговорящего сообщества: @vuejs_ru
Download Telegram
Forwarded from Denis Chernov
Такс ребятушки. Новый исторический момент в мире Vue.js: Vue language tools v2

- Что это такое и как оно нас касается?
не так давно вы могли слышать о обнове Volar.js. Если нет, то не страшно. Команда Vue очень заточена под улучшения экосистемы целиком, а не только Vue-разработчиков. Поэтому Джонсон (которого вы можете знать по тем самым обновлениям с перфом реактивности во Vue3) закорешился с мейнтенейром Astro и мейнтейнером MDX. Что этих крутышей связывает? Им нужно встраивать несколько языков в рамках одного файла и поддерживать при этом высокий DX. На самом деле это задача не из простых. И во Vue это проявлялось в VSC тем, что был злополучный Takeover Mode. Это особый режим который перекрывает плагин TS и вынуждал нас включать 2 плагина и отключать родной от TS.

Конечно все это пугало новичков. Но полтора месяца назад было объявляено, что эту проблему смогли побороть в рамках Volar 2. Теперь плагин использует возможности самого lsp от TS и не требует перекрытия плагином. С этого момента я оч ждал обновы language-tools.

https://github.com/vuejs/language-tools/releases/tag/v2.0.0
И вот час назад этот релиз вышел:
БОЛЬШЕ НИКАКОГО TAKEOVER MODE

+ к этому
- мы получаем родные для TS преобразования от TS плагина
- правит множество багов связанных с интеграцией и кастомным языком для VSC
- Больше нет плагина Volar - так как это более глобальный проект о фреймворке для поддержания авторов фреймворков. Теперь плагин это "Vue - Official"
- можете смело удалять расширение "TypeScript Vue Plugin"

Как это отразится на пользователях JetBrains? Пока сказать сложно, но буквально недавно была конференция у брейнсов, где скорее всего эту тему поднимали. Очень хочется верить, что это отразится на DX в IDE у JetBrains, так как они уже почти год переехали на Vue language server

PS. Вышло всего час назад, так что вполне возможны баги :D
PPS. Багов выше крыши. Советую переждать недельку!
6👍4
Документация Vue наконец получила долгожданный обновленный перевод на русский язык, который теперь доступен по ссылке ниже, а также в шапке основной англоязычной версии в блоке переводов:

https://ru.vuejs.org/

До этого на русском языке была доступна только устаревшая документация, написанная практически сразу после первоначального релиза Vue 3, но с тех пор многое изменилось, поэтому теперь рекомендуется читать именно обновленную документацию.

Переведены все основные разделы, но в документации все еще есть ошибки и неточности, например, на странице “Component v-model” описана работа функции toValue.

Если вы встретили ошибку/опечатку/неточность, о которой еще не написано в issues - можно создать его, либо исправить самому, прислав Pull Request.

Спасибо всем, кто помогал и принимал участие в переводе!
🔥326👍4🎉3🙏1
Через пол часа начинается митап от MSK Vue.js

Смотреть онлайн трансляцию можно по ссылке: https://youtube.com/live/7SWzCjDPtoQ
🔥64🌚1
Vue 3.5 alpha.1 - Reactive Props Destructure
#vue_3_5 #changelog

Начали выходить alpha и beta версии нового Vue 🎉

Первое обновление - Reactive Props Destructure теперь доступен без экспериментального флага. Позволяет более красиво описывать значения по умолчанию в SFC Setup + TS.

RFC был спорный и долго горячо обсуждался. При компиляции обращение к переменой превращается в обращение к полю объекта.

Обсуждался ещё с 3.2 и был ранее добавлен под экспериментальным флагом в 3.3.

<script setup lang="ts">
// Before
const props = withDefaults(defineProps<
foo?: string
>(), {
foo: 'foo'
})

// After
const { foo = 'foo' } = defineProps<
foo?: string
>()

// foo <==> props.foo
</script>


- RFC: https://github.com/vuejs/rfcs/discussions/502
👍6👎2🔥2😢1
Vue 3.5.0-alpha.3 - Появился новый компосабл useId() для генерации ID в компонентах.
#vue_3_5 #changelog

const inputId = useId()


Зачем генерировать ID?

Типичный use-case - id элементов, например, форм и атрибуты для доступности.

Почему не просто `Math.random()` или `nanoid()`?

Результат получается невоспроизводимым, что усложняет тестирование и не работает на SSR

Почему не просто `id++`?

Такое решение может приводить к дублированию ID. Например, и в вашем приложении, и в UI фреймворке могли сделать input-label-${id++}.

Чтобы облегчить жизнь разработчиков, добавили долгожданный useId, возвращающий уникальный ID, префикс которого можно настроить в конфиге Vue приложения.

const app = createApp(App)
app.config.idPrefix = 'my-app'
// useId() === "my-app:0"


- PR: https://github.com/vuejs/core/pull/11404/
👍9🔥53🤔1🎉1🕊1🌚1
Vue 3.5.0-alpha.3: появился 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
👍8🤔8🔥5
Vue 3.5: появился отдельный API для работы с cleanup-функциями в watch/watchEffect - 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
🔥15🤔2👍1
Работа с картинками в шаблоне
!src #help

1. Почему такой код не работает? Картинка точно есть по этому, и без переменной работает!
<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
👍84🔥2👏1🤔1
Отличия ref и reactive
!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👏32🤔1
Зачем Pinia, если можно написать свой стор?
!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🔥32