Перевод RFC в котором предложено и описывается Composition API. Оригинал: Composition API RFC
Представляем Composition API: набор аддитивных API на основе функций, которые позволяют гибко комбинировать логику компонентов.
Посмотрите Vue Mastery’s Vue 3 Essentials Course. Скачать Vue 3 шпаргалку.
<template> <button @click="increment"> Count is: {{ state.count }}, double is: {{ state.double }} </button> </template> <script> import { reactive, computed } from 'vue' export default { setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } } } </script>
Мы все любим, как с помощью Vue можно очень легко создавать небольшие и средние приложения на одном дыхании. Но сегодня, по мере роста популярности Vue, многие пользователи также используют Vue для создания крупномасштабных проектов — проектов, которые выполняются и поддерживаются в течение длительного периода времени командой из нескольких разработчиков. За прошедшие годы мы стали свидетелями того, как некоторые из этих проектов испытывают проблемы в рамках модели программирования, используемой текущим Vue API. Проблемы могут быть разделены на две категории:
API-интерфейсы, предложенные в этом RFC, предоставляют пользователям больше гибкости при организации кода компонента. Вместо того, чтобы заставлять всегда организовывать код по заранее определенным опциям, код теперь может быть организован в функциях, каждая из которых будет реализовывать определенный функционал. API также упрощают извлечение и повторное использование логики между компонентами или даже внешними компонентами. Мы покажем, как эти цели достигаются в разделе «Детальный дизайн».
Другой распространенный запрос от разработчиков, работающих над большими проектами, — лучшая поддержка TypeScript. Нынешний Vue API создает некоторые проблемы, когда речь заходит об интеграции с TypeScript, в основном из-за того, что Vue полагается на контекст this для предоставления свойств, и что использование this в компоненте Vue немного более магически (сложнее), чем в простом JavaScript (например, this внутри функций, вложенных в methods, указывает на экземпляр компонента, а не на объект methods). Другими словами, существующий Vue API просто не был разработан с учетом логического вывода типов, и это создает большую сложность при попытке адаптировать его для работы с TypeScript.
Большинство пользователей, которые сегодня используют Vue с TypeScript, используют библиотеку vue-class-component, которая позволяет создавать компоненты как классы TypeScript (с помощью декораторов). При разработке 3.0 мы пытались предоставить встроенный Class API для лучшего решения проблем типизации в предыдущем (удаленном) RFC. Однако, как мы заметили, что для того, чтобы Class API мог разрешать проблемы с типизацией, он должен полагаться на декораторы — что является очень нестабильным второго этапа предложения (proposal) с большой неопределенностью в отношении деталей его реализации. Это делает его довольно рискованным для реализации. (Подробнее о проблемах типов Class API здесь)
Для сравнения, API, предложенные в этом RFC, используют в основном простые переменные и функции, которые естественно дружественны к типу. Код, написанный с использованием предложенных API-интерфейсов, может иметь полный вывод типа без необходимости подсказок типа вручную. Это также означает, что код, написанный с использованием предложенных API-интерфейсов, будет выглядеть практически одинаково в TypeScript и обычном JavaScript, поэтому даже пользователи, не использующие TypeScript, потенциально смогут извлечь выгоду из типов для лучшей поддержки IDE.
Вместо того, чтобы вводить новые концепции, предлагаемое API больше направлено на то, чтобы представить основные возможности Vue, такие как создание и наблюдение за реактивным состоянием, в качестве отдельных функций. Здесь мы представим ряд наиболее фундаментальных API и способы их использования вместо опций версии 2.x для выражения внутрикомпонентной логики. Обратите внимание, что этот раздел посвящен ознакомлению с основными идеями, поэтому в нем подробно не рассматривается каждое новое API. Полные спецификации API можно найти в разделе API Reference.
Начнем с простой задачи: объявление какой-то реактивной переменной.
import { reactive } from 'vue' // reactive state const state = reactive({ count: 0 })
reactive является эквивалентом текущего API-интерфейса Vue.observable() в 2.x, переименованного во избежание путаницы с наблюдаемыми (observables) объектами RxJS. Здесь возвращаемая переменная state является реактивным объектом, с которым все пользователи Vue должны быть знакомы.
Основным вариантом использования реактивной переменной в Vue является то, что мы можем использовать ее во время рендеринга. Благодаря отслеживанию зависимостей представление автоматически обновляется при изменении реактивной переменной. Рендеринг чего-либо в DOM считается «побочным эффектом»: наша программа изменяет переменную, внешнею по отношению к самой программе (DOM). Чтобы применить и автоматически повторно применить побочный эффект в зависимости от реактивной переменной, мы можем использовать watch API:
import { reactive, watch } from 'vue' const state = reactive({ count: 0 }) watch(() => { document.body.innerHTML = `count is ${state.count}` })
watch ожидают функцию, которая применяет желаемый побочный эффект (в нашем случае, определение innerHTML). Она немедленно выполняет функцию и отслеживает все свойства реактивной переменной, которую она использовала во время выполнения, в качестве зависимостей. Здесь, state.count будет отслеживаться как зависимость для этого наблюдателя после первоначального выполнения. Когда в будущем будет изменена state.count, внутренняя функция снова будет выполнена.
Это сама суть системы реактивности Vue. Когда вы возвращаете объект из data() в компоненте, он внутренне становится реактивным с помощью reactive(). Шаблон компилируется в функцию рендеринга (воспринимается как более эффективный innerHTML), которая использует эти реактивные свойства.
Продолжая приведенный выше пример, мы обработаем пользовательский ввод:
function increment() { state.count++ } document.body.addEventListener('click', increment)
Но с системой шаблонов Vue нам не нужно связываться с innerHTML или вручную подключать прослушиватели событий (event listeners). Давайте упростим пример с помощью гипотетического метода renderTemplate, чтобы мы могли сосредоточиться на реактивности:
import { reactive, watch } from 'vue' const state = reactive({ count: 0 }) function increment() { state.count++ } const renderContext = { state, increment } watch(() => { // hypothetical internal code, NOT actual API renderTemplate( `<button @click="increment">{{ state.count }}</button>`, renderContext ) })
Иногда нам нужно состояние, которое зависит от другого состояния — в Vue это обрабатывается с помощью вычисляемых свойств (computed). Чтобы напрямую создать вычисляемое значение, мы можем использовать computed API:
import { reactive, computed } from 'vue' const state = reactive({ count: 0 }) const double = computed(() => state.count * 2)
Что вернет computed? Предположим, как computed реализован внутри, это будет что-то вроде такого:
// упрощенный pseudo код function computed(getter) { let value watch(() => { value = getter() }) return value }
Но мы знаем, что это не сработает: если значение value является примитивным типом (например число), то его связь с логикой обновления внутри computed будет потеряна после его возврата (оператор return). Это потому, что примитивные типы JavaScript передаются по значению, а не по ссылке:
Та же проблема возникает, когда объекту присваивается значение как свойство. Реактивное значение не будет очень полезным, если оно не сможет сохранить свою реактивность при назначении в качестве свойства или при возврате из функций. Чтобы убедиться, что мы всегда можем прочитать последнее значение вычисления, нам нужно обернуть фактическое значение в объекте и вместо этого вернуть этот объект:
// упрощенный pseudo код function computed(getter) { const ref = { value: null } watch(() => { ref.value = getter() }) return ref }
Кроме того, нам также необходимо перехватывать операции чтения/записи в свойстве объекта .value для отслеживания зависимостей и уведомления об изменениях (здесь для простоты код опущен). Теперь мы можем передавать вычисленное значение по ссылке, не беспокоясь о потере реактивности. Компромисс в том, что для получения последнего значения нам теперь нужно получить к нему доступ через .value:
const double = computed(() => state.count * 2) watch(() => { console.log(double.value) }) // -> 0 state.count++ // -> 2
Здесь double — это объект, который мы называем «ref», поскольку он служит реактивной ссылкой на внутреннее значение, которое он содержит.
Возможно, вы знаете, что в Vue уже есть понятие «refs», но только для ссылки на элементы DOM или экземпляры компонентов в template («refs шаблона»). Прочтите это, чтобы увидеть, как новая система ссылок refs может использоваться как для логического состояния, так и для шаблона ссылок refs.
В дополнение к вычисляемым ссылкам, мы также можем напрямую создавать простые изменяемые ссылки, используя ref API:
const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1
Мы можем выставить ref как свойство в контексте визуализации. Внутри Vue будет выполнять специальную обработку для refs, так что, когда refs встречается в контексте рендеринга, контекст напрямую раскрывает свое внутреннее значение. Это означает, что в шаблоне мы можем напрямую написать {{ count }} вместо {{count.value}}.
Вот версия того же примера счетчика, использующего ref вместо reactive:
import { ref, watch } from 'vue' const count = ref(0) function increment() { count.value++ } const renderContext = { count, increment } watch(() => { renderTemplate( `<button @click="increment">{{ count }}</button>`, renderContext ) })
Кроме того, когда ref вкладывается как свойство в реактивный объект, она также автоматически разворачивается при доступе:
const state = reactive({ count: 0, double: computed(() => state.count * 2) }) // no need to use `state.double.value` console.log(state.double)
Наш код на данный момент уже предоставляет рабочий UI, который может обновляться на основе пользовательского ввода, но код выполняется только один раз и не может использоваться повторно. Если мы хотим повторно использовать логику, следующим разумным шагом будет рефакторинг его в функцию:
import { reactive, computed, watch } from 'vue' function setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } } const renderContext = setup() watch(() => { renderTemplate( `<button @click="increment"> Count is: {{ state.count }}, double is: {{ state.double }} </button>`, renderContext ) })
Обратите внимание, что приведенный выше код не зависит от наличия экземпляра компонента. Действительно, API, представленные до сих пор, могут использоваться вне контекста компонентов, что позволяет нам использовать систему реактивности Vue в более широком диапазоне сценариев.
Теперь, если мы оставим задачи вызова setup(), создания watcher и рендеринга template в фреймворке, мы можем определить компонент только с помощью функции setup() и template:
<template> <button @click="increment"> Count is: {{ state.count }}, double is: {{ state.double }} </button> </template> <script> import { reactive, computed } from 'vue' export default { setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } } } </script>
Это знакомый нам формат однофайлового компонента, только с логической частью (<script>) выраженной в другом формате. Синтаксис шаблона остается точно таким же. <style> пропущен, но будет работать точно так же.
До сих пор мы рассмотрели аспект чистого состояния компонента: реактивное состояние, вычисленное состояние и состояние изменения при вводе пользователем. Но компонент также может выполнять другие действия — например, вывод в консоль, отправление запроса ajax или подключение слушителя событий в window. Эти дополнительные действия обычно выполняются в следующие временные промежутки:
Мы знаем, что мы можем использовать watch API для применения дополнительных действий на основе изменений состояния. Что касается выполнения этих действий в различных хуках жизненного цикла, мы можем использовать выделенные API по имени начинающееся с onXXX (которые напрямую отражают существующие параметры жизненного цикла):
import { onMounted } from 'vue' export default { setup() { onMounted(() => { console.log('component is mounted!') }) } }
Эти методы регистрации жизненного цикла могут использоваться только во время вызова setup хуков. Они автоматически определяет текущий экземпляр, вызывающий setup хук, используя внутреннее глобальное состояние. Они специально разработаны таким образом, чтобы уменьшить проблемы при извлечении логики во внешние функции.
Более подробную информацию об этом API можно найти в API Reference. Тем не менее, мы рекомендуем закончить чтение следующих разделов, прежде чем углубляться в детали дизайна.
На этом этапе мы воспроизвели API компонента с импортированными функциями, но зачем? Определение компонентов с опциями кажется гораздо более организованным, чем смешивание всего вместе в большую функцию!
Это понятное первое впечатление. Но, как уже упоминалось в разделе «Мотивации», мы считаем, что Composition API на самом деле приводит к более организованному коду, особенно в сложных компонентах. Здесь мы попытаемся объяснить, почему.
Давайте сделаем шаг назад и рассмотрим, что мы на самом деле имеем в виду, когда говорим об «организованном коде». Конечная цель сохранения кода должна состоять в том, чтобы сделать код более легким для чтения и понимания. И что мы подразумеваем под «пониманием» кода? Можем ли мы действительно утверждать, что «понимаем» компонент только потому, что знаем, какие параметры он содержит? Вы когда-нибудь сталкивались с большим компонентом, автором которого является другой разработчик (например, этот), и как легко вам было его понять?
Подумайте о том, как бы вы бы помогли коллеге-разработчику пройти через большой компонент, подобному указанному выше. Скорее всего, вы начнете с «этот компонент имеет дело с X, Y и Z» вместо того что бы сказать «у этого компонента есть эти свойства данных, эти вычисляемые свойства и эти методы». Когда дело доходит до понимания компонента, мы больше заботимся о том, «что пытается сделать компонент» (какие были намерения, стоящих за кодом), а не о том, «какие опции использует компонент». Хотя код, написанный с использованием API, основанного на опциях, естественным образом отвечает последнему, он довольно плохо справляется с выражением первого утверждения.
Давайте определим «X, Y и Z», с которыми имеет дело компонент, как логические проблемы. Проблема читабельности, как правило, отсутствует в небольших одноцелевых компонентах, поскольку весь компонент имеет дело с одной логической проблемой. Тем не менее, проблема становится гораздо более заметной в сложных случаях использования. Возьмите в качестве примера файловый менеджер Vue CLI. Компонент имеет дело со многими различными логическими задачами:
Можете ли вы мгновенно распознать и найтие эти логические проблемы, прочитав код, основанный на опциях? Это будет сложно. Вы заметите, что код, связанный с определенной логической проблемой, часто фрагментирован и разбросан повсюду. Например, функция «Создать новую папку» использует два свойства data, одно вычисляемое свойство и метод, причем этот метод определен на расстоянии более ста строк от свойств данных.
Если мы раскрасим каждую из этих логических задач в цвет, мы заметим, насколько они фрагментированы, когда выражены с помощью опций компонента:
Такая фрагментация — именно то, что затрудняет понимание и поддержание сложного компонента. Принудительное разделение с помощью опций скрывает основные логические проблемы. Кроме того, работая над одной логической проблемой, мы должны постоянно «перепрыгивать» вокруг блоков опций, чтобы найти части, связанные с этой проблемой.
Примечание: исходный код, вероятно, может быть улучшен в нескольких местах, но мы показываем его в последнем коммите (на момент написания статьи) без изменений, чтобы предоставить пример фактического рабочего кода, который мы написали сами.
Было бы намного лучше, если бы мы могли разместить код, основываясь на логической задачей который он решает. И это именно то, что для чего мы создали Composition API. Функцию «Создать новую папку» можно теперь записать так:
function useCreateFolder (openFolder) { // originally data properties const showNewFolder = ref(false) const newFolderName = ref('') // originally computed property const newFolderValid = computed(() => isValidMultiName(newFolderName.value)) // originally a method async function createFolder () { if (!newFolderValid.value) return const result = await mutate({ mutation: FOLDER_CREATE, variables: { name: newFolderName.value } }) openFolder(result.data.folderCreate.path) newFolderName.value = '' showNewFolder.value = false } return { showNewFolder, newFolderName, newFolderValid, createFolder } }
Обратите внимание на то, как вся логика, связанная с функцией создания новой папки, теперь расположена и помещена в одну функцию. Функция также несколько самодокументируется из-за своего описательного имени. Это то, что мы называем композиционной функцией (composition function). Рекомендуется начинать имя функции с use, чтобы указать, что это составная функция. Этот шаблон может быть применен ко всем другим логическим проблемам в компоненте, что приводит к ряду красиво отделенных функций:
Это сравнение исключает операторы импорта и функцию setup(). Полный компонент, повторно реализованный с использованием Composition API, можно найти здесь.
Код для каждой логической задачи теперь объединен в композиционной функции. Это значительно снижает необходимость в постоянных «скачках» при работе с большим компонентом. Компоновочные функции также могут быть свернуты в редакторе, чтобы сделать сканирование компонента намного проще:
export default { setup() { // ... } } function useCurrentFolderData(networkState) { // ... } function useFolderNavigation({ networkState, currentFolderData }) { // ... } function useFavoriteFolder(currentFolderData) { // ... } function useHiddenFolders() { // ... } function useCreateFolder(openFolder) { // ... }
Функция setup() теперь в основном служит точкой входа, где вызываются все функции композиции:
export default { setup () { // Network const { networkState } = useNetworkState() // Folder const { folders, currentFolderData } = useCurrentFolderData(networkState) const folderNavigation = useFolderNavigation({ networkState, currentFolderData }) const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData) const { showHiddenFolders } = useHiddenFolders() const createFolder = useCreateFolder(folderNavigation.openFolder) // Current working directory resetCwdOnLeave() const { updateOnCwdChanged } = useCwdUtils() // Utils const { slicePath } = usePathUtils() return { networkState, folders, currentFolderData, folderNavigation, favoriteFolders, toggleFavorite, showHiddenFolders, createFolder, updateOnCwdChanged, slicePath } } }
Конечно, это код, который нам не нужно было писать при использовании API с опциями. И обратите внимание, что функция setup почти читается как словесное описание того, что пытается сделать компонент — это информация, так же полностью отсутствовала в версии, основанной на опциях. Вы можете четко видеть поток зависимостей между композиционными функциями на основе передаваемых аргументов. Наконец, оператор return служит единственным местом для проверки того, что доступно шаблону.
При одинаковой функциональности компонент, определенный с помощью опций, и компонент, определенный с помощью композиционных функций, проявляют два разных способа выражения одной и той же базовой логики. API на основе параметров вынуждает нас организовывать код на основе типов опций (option types), в то время как API-интерфейс Composition позволяет нам организовывать код на основе логических соображений (logical concerns).
Composition API чрезвычайно гибок, когда дело доходит до извлечения и повторного использования логики между компонентами. Вместо того, чтобы полагаться на магический контекст this, функция композиции опирается только на свои аргументы и глобально импортированные API-интерфейсы Vue. Вы можете повторно использовать любую часть вашей компонентной логики, просто экспортировав ее как функцию. Вы даже можете получить эквивалент extends, экспортируя всю функцию setup.
Давайте посмотрим на пример: отслеживание положения мыши.
import { ref, onMounted, onUnmounted } from 'vue' export function useMousePosition() { const x = ref(0) const y = ref(0) function update(e) { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }
Вот как компонент может использовать эту функцию:
import { useMousePosition } from './mouse' export default { setup() { const { x, y } = useMousePosition() // other logic... return { x, y } } }
В версии Composition API примера файлового эксплорера мы перенесли некоторый служебный код (например, usePathUtils и useCwdUtils) во внешние файлы, потому что мы нашли их полезными для других компонентов.
Аналогичное повторное использование логики также может быть достигнуто с использованием существующих шаблонов, таких как миксины, компоненты высшего порядка или компоненты без рендеринга (через scoped слоты). В интернете достаточно информации, объясняющей эти паттерны, поэтому мы не будем их здесь подробно описывать. Более общая идея заключается в том, что каждый из этих шаблонов имеет соответствующие недостатки по сравнению с функциями композиции:
По сравнению с Composition API:
Composition API может использоваться вместе с существующим API на основе опций.
Многие плагины Vue сегодня внедряют свойства в this. Например, Vue Router внедряет this.$route и this.$router, а Vuex внедряет this.$store. Это усложняет получение типов, так как каждый плагин требует, чтобы пользователь увеличивал типизацию Vue для введенных свойств.
При использовании Composition API this не используется. Вместо этого плагины будут использовать provide
и inject
внутри и предоставят функцию композиции. Ниже приведен гипотетический код для плагина:
const StoreSymbol = Symbol() export function provideStore(store) { provide(StoreSymbol, store) } export function useStore() { const store = inject(StoreSymbol) if (!store) { // throw error, no store provided } return store }
И код использования:
// provide store at component root // const App = { setup() { provideStore(store) } } const Child = { setup() { const store = useStore() // use the store } }
Обратите внимание, что store также может быть предоставлено с помощью provide уровня приложения, предложенного в Global API change RFC, но API useStore в компоненте где он используется будет таким же.
Ref технически является единственной «новой» концепцией, представленной в этом RFC. Оно введено для того, чтобы создавать реактивные переменные, не полагаясь на доступ к this. Недостатки:
Мы обсудили, возможно ли полностью избежать концепции Ref и использовать только реактивные объекты (reactive), однако:
Понятно, что пользователи могут запутаться в отношении того, что использовать ref или reactive. Первое, что нужно знать, это то, что вам нужно будет понять и то, и другое, чтобы эффективно использовать Composition API. Использование одного из них, скорее всего, приведет к эзотерическим обходным путям или к новым колесам.
Разницу между использованием ref и reactive можно несколько сравнить с тем, как вы будете писать стандартную логику JavaScript:
// style 1: несколько переменных let x = 0 let y = 0 function updatePosition(e) { x = e.pageX y = e.pageY } // --- в сравнение с --- // style 2: одиночный объект const pos = { x: 0, y: 0 } function updatePosition(e) { pos.x = e.pageX pos.y = e.pageY }
Однако проблема с использованием только reactive заключается в том, что потребитель композиционной функции должен постоянно сохранять ссылку на возвращаемый объект, чтобы сохранить реактивность. Объект не может быть разрушен или распространен:
// композиционная функция function useMousePosition() { const pos = reactive({ x: 0, y: 0 }) // ... return pos } // используемый ее компонент export default { setup() { // реактивность потеряна! const { x, y } = useMousePosition() return { x, y } // реактивность потеряна! return { ...useMousePosition() } // это единственный способ сохранить реактивность. // вы должны return `pos` как есть и ссылаться на x и y как `pos.x` и `pos.y` // in the template. return { pos: useMousePosition() } } }
API toRefs
предназначен для решения этого ограничения — он преобразует каждое свойство реактивного объекта в соответствующий ref:
function useMousePosition() { const pos = reactive({ x: 0, y: 0 }) // ... return toRefs(pos) } // x и y сейчас refs! const { x, y } = useMousePosition()
Подводя итог, можно выделить два жизнеспособных стиля:
На данном этапе мы полагаем, что еще слишком рано называть лучшей практикой использование ref, а не reactive. Мы рекомендуем вам придерживаться стиля, который лучше соответствует вашей ментальной модели, из двух вариантов выше. Мы будем собирать отзывы пользователей в реальном мире и в конечном итоге предоставим более четкие рекомендации по этой теме.
Некоторые пользователи выражают беспокойство по поводу того, что выражение return в setup() является многословным и ощущается как бесполезный шаблон.
Мы считаем, что явное использование return полезно для удобства поддержки кода. Это дает нам возможность явно контролировать то, что раскрывается в шаблоне, и служит отправной точкой при трассировке, где свойство шаблона определено в компоненте.
Были предложения автоматически выставлять переменные, объявленные в setup(), делая оператор return необязательным. Опять же, мы не думаем, что он должен быть по умолчанию, так как это противоречит интуиции стандартного JavaScript. Однако, есть возможные способы сделать его использование менее рутинным в пользовательском пространстве:
Многие пользователи отмечают, что, хотя интерфейс Composition API обеспечивает большую гибкость в организации кода, он также требует большей дисциплины от разработчика, чтобы «все сделать правильно». Некоторые опасаются, что API приведет к созданию спагетти-кода в неопытных руках. Другими словами, хотя интерфейс Composition API повышает верхнюю границу качества кода, он также понижает нижнюю границу.
Мы согласны с этим в определенной степени. Тем не менее, мы считаем, что:
Некоторые пользователи использовали контроллеры Angular 1 в качестве примеров того, как проект может привести к плохо написанному коду. Самое большое различие между Composition API и контроллерами Angular 1 заключается в том, что Composition API не зависит от общего контекста scope. Это значительно упрощает разделение логики на отдельные функции, что является основным механизмом организации кода JavaScript.
Любая JavaScript-программа начинается с входного файла (воспринимается как setup() для программы). Мы должны организовывать программу, разбивая ее на функции и модули, исходя из логических соображений. Composition API позволяет нам делать то же самое для кода компонента Vue. Другими словами, навыки написания хорошо организованного кода JavaScript напрямую влияют на навыки написания хорошо организованного кода Vue при использовании Composition API.
Composition API является исключительно аддитивным и не влияет на существующие API-интерфейсы 2.x. Он доступен как плагин 2.x через библиотеку @vue/composition
. Основная цель библиотеки — предоставить возможность экспериментировать с API и собирать отзывы. Текущая реализация соответствует данному RFC, но может содержать незначительные несоответствия из-за технических ограничений, связанных с подключением. Кроме того, могут появиться изменения по мере обновления этого RFC, поэтому мы не рекомендуем использовать его в производстве на данном этапе.
Мы намерены выпустить API как встроенное в 3.0. Он будет использоваться вместе с существующими опциями 2.x.
Для пользователей, которые предпочитают использовать исключительно Composition API в приложении, можно будет указать флаг во время компиляции, чтобы отбросить код, используемый только для параметров 2.x, и уменьшать размер библиотеки. Однако это совершенно необязательно.
API будет позиционироваться как расширенная функция, поскольку проблемы, на решение которых он нацелен, появляются главным образом в крупномасштабных приложениях. Мы не намерены пересматривать документацию, чтобы использовать ее по умолчанию. Вместо этого он будет иметь свой собственный выделенный раздел в документации.
Основная цель введения Class API состояла в том, чтобы предоставить альтернативный API, который поставляется с лучшей поддержкой вывода TypeScript. Однако тот факт, что компоненты Vue должны объединять свойства, объявленные из нескольких источников, в один контекст this, создает некоторую проблему даже с API на основе классов.
Одним из примеров является типы в props. Чтобы объединить props с this, мы должны либо использовать обобщенные аргументы для класса компонента, либо использовать декоратор.
Вот пример использования обобщенных аргументов:
interface Props { message: string } class App extends Component<Props> { static props = { message: String } }
Поскольку интерфейс, передаваемый универсальному аргументу, находится только в области типов, пользователю все равно необходимо предоставить объявление props во время выполнения для соединения с this. Это двойное объявление является излишним и неудобным.
Мы рассмотрели использование декораторов в качестве альтернативы:
class App extends Component<Props> { @prop message: string }
Использование декораторов создает зависимость от спецификации этапа 2 с большим количеством неопределенностей, особенно когда текущая реализация TypeScript полностью не синхронизирована с предложением TC39. Кроме того, в this.$props нет способа выставить типы props, объявленные с помощью декораторов, что нарушает поддержку TSX. Пользователи могут также предположить, что они могут объявить значение по умолчанию для @prop message: string = ‘foo’, когда технически это просто не может работать должным образом.
Кроме того, в настоящее время нет способа использовать контекстную типизацию для аргументов методов класса — это означает, что аргументы, передаваемые в функцию рендеринга класса render, не могут иметь предполагаемые типы, основанные на других свойствах класса.
API на основе функций обеспечивает тот же уровень возможностей логической композиции, что и React Hooks, но с некоторыми важными отличиями. В отличие от React Hooks, функция setup() вызывается только один раз. Это означает, что код, использующий Composition API:
Мы признаем креативность React Hooks, и это является основным источником вдохновения для этого RFC. Тем не менее, проблемы, упомянутые выше, существуют в его дизайне, и мы заметили, что модель реактивности Vue обеспечивает обходной путь.
Несмотря на совершенно разные подходы, Composition API и подход на основе компиляторов Svelte 3 фактически концептуально имеют много общего. Вот пример:
<script> import { ref, watch, onMounted } from 'vue' export default { setup() { const count = ref(0) function increment() { count.value++ } watch(() => console.log(count.value)) onMounted(() => console.log('mounted!')) return { count, increment } } } </script>
<script> import { onMount } from 'svelte' let count = 0 function increment() { count++ } $: console.log(count) onMount(() => console.log('mounted!')) </script>
Код Svelte выглядит более лаконичным, потому что во время компиляции он делает следующее:
Технически, мы можем сделать то же самое в Vue (и это возможно через пользовательские плагины Babel). Основной причиной, по которой мы этого не делаем, является соответствие стандартному JavaScript. Если мы извлекаете код из блока <script> файла Vue, мы хотим, чтобы он работал точно так же, как стандартный модуль ES. Код внутри блока Svelte <script>, с другой стороны, технически больше не является стандартным JavaScript. Есть ряд проблем, которые мы видим с этим подходом на основе компилятора:
Это ни в коем случае не говорит о том, что Svelte 3 — плохая идея — на самом деле, это очень инновационный подход, и мы высоко ценим работу Рича. Но исходя из конструктивных ограничений и целей Vue, мы должны делать различные компромиссы.
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…