Почему в Vue 3 Composition API — reactive() будет считаться не нужным
В новом RFC в котором описано Vue Composition API, для создания реактивных переменных было представлены две новые функции ref и reactive. В нем было описаны их преимущества и недостатки, а так же сказано:
На данном этапе мы полагаем, что еще слишком рано называть лучшей практикой использование ref, а не reactive. Мы рекомендуем вам придерживаться стиля, который лучше соответствует вашей ментальной модели, из двух вариантов выше. Мы будем собирать отзывы пользователей в реальном мире и в конечном итоге предоставим более четкие рекомендации по этой теме.
Данная статья описывает одно из мнений, почему функция reactive() может быть не нужной. Само мнение очень интересно, с точки зрения изучения нового API.
Оригинальная статья: Jason Yu — Thought on Vue 3 Composition API — `reactive()` considered harmful
Спонсор поста RackStore — поставщик широкого спектра IT-услуг на базе сети дата-центров, расположенных в Москве. Размещение серверов в дата-центре (Colocation). Бесперебойное электроснабжение. Круглосуточная охрана и видеонаблюдение, климат-контроль, резервными источниками питания, высокоскоростные каналы связи с безлимитным трафиком. Возможность межоператорских соединений. Техническая поддержка 24/7 и SLA-гарантии. Дата центры Tier2+ и Tier3. Высококвалифицированные специалисты осуществляют круглосуточную техническую поддержку на территории ЦОД
Vue.js выделяется среди других фреймворков своей интуитивной реактивностью. Сomposition API Vue 3 собирается снять некоторые ограничения в Vue 2 и предоставить более явное API.
Краткое введение в Composition API
Есть два способа создания реактивных переменных:
- reactive()
- ref() / computed()
reactive()
reactive(obj) вернет новый объект, который выглядит точно так же, как obj, но любые изменения нового объекта будут отслеживаться (например обновляться в шаблонах).
Для примера:
// template: {{ state.a }} - {{ state.b }} const state = reactive({ a: 3 }) // renders: 3 - undefined state.a = 5 state.b = 'bye' // renders: 5 - bye
Это работает точно так же, как data в Vue 2. За исключением того, что теперь мы можем добавлять к ним новые свойства, поскольку реактивность реализована с помощью прокси в Vue 3.
Ref
В Vue Composition API так же добавлен ref, который представляет собой просто объект с 1 свойством .value. Мы можем выразить его с помощью Typescript:
interface Ref<A> { value: A }
Есть два способа использования refs:
- ref()
- .value может иметь get/set.
- computed()
- .value будет только для чтения, если не указан setter.
Для примера:
const countRef = ref(0) // { value: 0 } const countPlusOneRef = computed(() => countRef.value + 1) // { value: 1 } countRef.value = 5 /* * countRef is { value: 5 } * countPlusOneRef is { value: 6 } (readonly) */
reactive() это плохо; ref это хорошо.
Этот раздел статьи является только моим мнением о использование Composition API после создания нескольких проектов с его помощью. Попробуйте сами и дайте мне знать, если вы согласны или не согласны.
Прежде чем я сам не попробовал Composition API, я думал, что reactive() будет API, которое все будут использовать в конечном итоге, так как он не требует лишнего атрибута .value. Удивительно, но после создания нескольких проектов с Composition API, я ни разу не использовал reactive()!
Вот 3 причины почему:
- Удобство — ref() позволяет объявить новую реактивную переменную на лету.
- Гибкость — ref() разрешает полную замену объекта
- Ясность — .value заставляет вас осознавать, то что вы делаете
1. Удобство
Composition api предлагается предоставить способ группировки кода в соответствии с их функцией в компоненте вместо их функции в Vue. Опции Vue Api 2.X группируют код в data, computed, methods, жизненные циклы и т. д. Это делает практически невозможным группирование кода по функциям. Взгляните на следующее изображение, на нем кода закрашен определенным цветом соответствие с решаемой задаче:
Рассмотрим следующие примеры:
const state = reactive({ count: 0, errorMessage: null, }) setTimeout(() => state.count++, 1000) watch(state.count, count => { if (count > 10) { state.errorMessage = 'Larger than 10.' } })
Если мы будем использовать reactive() для хранения нескольких свойств. Легко попасть в ловушку группировки вещей по функциям, а не по свойствам. Скорее всего, вы будете прыгать по базе кода, чтобы изменить этот реактивный объект. Что сделает процесс разработки менее плавным.
const count = ref(0) setTimeout(() => count.value++, 1000) const errorMessage = ref(null) watch(count, count => { if (count > 10) { errorMessage.value = 'Larger than 10.' } })
С другой стороны, ref() позволяет нам вводить новые переменные на лету. Из приведенного выше примера я представляю переменные только по мере необходимости. Это делает процесс разработки намного более плавным и интуитивно понятным.
2. Гибкость
Сначала я думал, что единственная цель ref() состоит в том, чтобы примитивные значения были реактивными. Но также может быть очень удобно использовать ref() с объектами.
Рассмотрим:
const blogPosts = ref([]) blogPosts.value = await fetchBlogPosts()
Если мы хотим сделать то же самое с reactive, нам нужно вместо этого изменить массив.
const blogPosts = reactive([]) for (const post of (await fetchBlogPosts())) { blogPosts.push(post) }
или с нашим «любимым» Array.prototype.splice()
const blogPosts = reactive([]) blogPosts.splice(0, 0, ...(await fetchBlogPosts()))
Как показано, с ref() проще работать так как в этом случае, вы можете просто заменить весь массив новым. Если это вас не убеждает, представьте, что blogPosts нужно разбить на страницы:
watch(page, page => { // remove everything from `blogPosts` while (blogPosts.length > 0) { blogPosts.pop() } // add everything from new page for (const post of (await fetchBlogPostsOnPage(page))) { blogPosts.push(post) } })
или с нашим лучшим другом splice
watch(page, page => { blogPosts.splice(0, blogPosts.length, ...(await fetchBlogPostsOnPage(page))) })
Но если мы используем ref()
watch(page, page => { blogPosts.value = await fetchBlogPostsOnPage(page) })
Что очень гибко для работы.
3. Ясность
reactive() возвращает объект, с которым мы будем взаимодействовать так же, как мы взаимодействуем с другим нереактивным объектом. Это круто, но на практике это может сбить с толку, если мы имеем дело с другими нереактивными объектами.
watch(() => { if (human.name === 'Jason') { if (!partner.age) { partner.age = 30 } } })
Мы не можем действительно сказать, будет ли реактивным human или partner. Но если мы отказываемся от использования reactive() и последовательно используем ref(), у нас не будет этой проблемы.
поначалу .value может показаться многословным; но он помогает напомнить нам, что мы имеем дело с реактивностью.
watch(() => { if (human.value.name === 'Jason') { if (!partner.age) { partner.age = 30 } } })
Теперь становится очевидным, что human будет реактивным, а не partner.
Заключение
Приведенные выше замечания и мнения носят предварительный характер. Как вы думаете? Вы согласны, что ref() будет доминировать в Vue 3? Или вы думаете, что reactive() будет предпочтительным?
Дай мне знать в комментариях! Я хотел бы услышать больше ваших идей!