Reactivity Transform
Утратившая силу экспериментальная функция
Reactivity Transform была экспериментальной функцией и уже устарела. Пожалуйста, ознакомьтесь с обоснованием.
В конечном итоге она будет удалена из ядра Vue в одном из будущих минорных выпусков.
- Чтобы отказаться от неё, ознакомьтесь с этим инструментом командной строки, который может автоматизировать этот процесс.
- Если вы всё ещё собираетесь использовать эту функцию, то теперь она доступна через плагин Vue Macros.
Composition-API-specific
Reactivity Transform является специфической функцией Composition API и требует этапа сборки.
Ссылки против реактивных переменных
С момента появления Composition API одним из основных нерешённых вопросов является использование реактивных ссылок по сравнению с использованием реактивных объектов. Легко потерять реактивность при деструктуризации реактивных объектов, в то время как использование .value
везде при использовании реактивных ссылок может быть громоздким. Кроме того, .value
легко пропустить, если не использовать систему типов.
Vue Reactivity Transform - это преобразование во время компиляции, которое позволяет нам писать код, подобный этому:
vue
<script setup>
let count = $ref(0)
console.log(count)
function increment() {
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Метод $ref()
здесь является макросом на этапе компиляции: это не настоящий метод, который будет вызван во время выполнения. Вместо этого компилятор Vue использует его как подсказку, чтобы рассматривать результирующую переменную count
как реактивную переменную..
К реактивным переменным можно обращаться и переназначать их точно так же, как и обычные переменные, но эти операции компилируются в рефссылки с .value
. Например, часть <script>
вышеприведённого компонента компилируется в:
js
import { ref } from 'vue'
let count = ref(0)
console.log(count.value)
function increment() {
count.value++
}
Каждый Reactivity API, возвращающий рефссылки, будет иметь эквивалент макроса с префиксом $
. Эти API включают в себя:
ref
->$ref
computed
->$computed
shallowRef
->$shallowRef
customRef
->$customRef
toRef
->$toRef
Эти макросы доступны глобально и не нуждаются в импорте при включении Reactivity Transform, но вы можете импортировать их из vue/macros
, если хотите быть более явными:
js
import { $ref } from 'vue/macros'
let count = $ref(0)
Деструктуризация с $()
Обычно функция композиции возвращает объект ref-ссылок и использует деструктуризацию для извлечения этих ref-ссылок. Для этого в Reactivity Transform предусмотрен макрос $()
:
js
import { useMouse } from '@vueuse/core'
const { x, y } = $(useMouse())
console.log(x, y)
Скомпилированный вывод:
js
import { toRef } from 'vue'
import { useMouse } from '@vueuse/core'
const __temp = useMouse(),
x = toRef(__temp, 'x'),
y = toRef(__temp, 'y')
console.log(x.value, y.value)
Обратите внимание, что если x
уже является ссылкой, то toRef(__temp, 'x')
просто вернет её как есть, и никакой дополнительной ссылки создано не будет. Если деструктурированное значение не является ссылкой (например, функция), он всё равно будет работать — значение будет обёрнуто в ссылку, так что остальная часть кода будет работать, как и ожидалось.
Деструктуризация $()
работает как с реактивными объектами, так и с обычными объектами, содержащими ссылки.
Преобразование существующих ссылок в реактивные переменные с помощью $()
В некоторых случаях мы можем иметь обёрнутые функции, которые также возвращают ссылки. Однако компилятор Vue не сможет заранее узнать, что функция будет возвращать ссылку. В таких случаях макрос $()
также может быть использован для преобразования всех существующих ссылок в реактивные переменные:
js
function myCreateRef() {
return ref(0)
}
let count = $(myCreateRef())
Деструктуризация реактивных параметров
Есть две болевые точки при текущем использовании defineProps()
в <script setup>
:
Как и в случае с
.value
, вам всегда необходимо обращаться к параметрам какprops.x
, чтобы сохранить реактивность. Это означает, что вы не можете деструктурироватьdefineProps
, потому что полученные в результате деструктурированные переменные не являются реактивными и не будут обновляться.При использовании объявления параметра только для типа не существует простого способа объявить значения по умолчанию для параметра. Именно для этой цели мы ввели API
withDefaults()
, но его по-прежнему неудобно использовать.
Мы можем решить эти проблемы, применив преобразование во время компиляции, когда defineProps используется с деструктуризацией, аналогично тому, что мы видели ранее с $()
:
html
<script setup lang="ts">
interface Props {
msg: string
count?: number
foo?: string
}
const {
msg,
// значение по умолчанию просто работает
count = 1,
// локальный псевдоним тоже работает
// здесь мы присваиваем `props.foo` псевдониму `bar`
foo: bar
} = defineProps<Props>()
watchEffect(() => {
// будет регистрироваться при каждом изменении параметров
console.log(msg, count, bar)
})
</script>
Вышеупомянутое будет скомпилировано в следующий эквивалент объявления времени выполнения:
js
export default {
props: {
msg: { type: String, required: true },
count: { type: Number, default: 1 },
foo: String
},
setup(props) {
watchEffect(() => {
console.log(props.msg, props.count, props.foo)
})
}
}
Сохранение реактивности за пределами функций
Хотя реактивные переменные избавляют нас от необходимости везде использовать .value
, это создает проблему «потери реактивности», когда мы передаем реактивные переменные через границы функции. Это может произойти в двух случаях:
Передача в функцию в качестве аргумента
Учитывая функцию, которая ожидает ссылку в качестве аргумента, например:
ts
function trackChange(x: Ref<number>) {
watch(x, (x) => {
console.log('x changed!')
})
}
let count = $ref(0)
trackChange(count) // не работает!
Приведённый выше случай не будет работать должным образом, поскольку он компилируется так:
ts
let count = ref(0)
trackChange(count.value)
Здесь count.value
передается как число, тогда как trackChange
ожидает фактическую ссылку. Это можно исправить, обернув count
в $$()
перед его передачей:
diff
let count = $ref(0)
- trackChange(count)
+ trackChange($$(count))
Вышеупомянутое компилируется в:
js
import { ref } from 'vue'
let count = ref(0)
trackChange(count)
Как мы видим, $$()
— это макрос, который служит подсказкой по выходу: к реактивным переменным внутри $$()
не будет добавлен .value
.
Возврат внутри области функции
Реактивность также может быть потеряна, если реактивные переменные используются непосредственно в возвращаемом выражении:
ts
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// слушаем mousemove...
// не работает!
return {
x,
y
}
}
Приведённый выше оператор return
компилируется в:
ts
return {
x: x.value,
y: y.value
}
Чтобы сохранить реактивность, мы должны возвращать фактические ссылки, а не текущее значение во время возврата.
Опять же, мы можем использовать $$()
, чтобы исправить это. В этом случае $$()
можно использовать непосредственно в возвращаемом объекте — любая ссылка на реактивные переменные внутри вызова $$()
сохранит ссылку на их базовые ссылки:
ts
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// слушаем mousemove...
// исправлено
return $$({
x,
y
})
}
Использование $$()
для деструктурированных параметров
$$()
работает с деструктурированными свойствами, поскольку они также являются реактивными переменными. Компилятор преобразует его с помощью toRef
для повышения эффективности:
ts
const { count } = defineProps<{ count: number }>()
passAsRef($$(count))
компилируется в:
js
setup(props) {
const __props_count = toRef(props, 'count')
passAsRef(__props_count)
}
Интеграция TypeScript
Vue предоставляет типизацию для этих макросов (доступно глобально), и все типы будут работать должным образом. Несовместимости со стандартной семантикой TypeScript нет, поэтому синтаксис будет работать со всеми существующими инструментами.
Это также означает, что макросы могут работать в любых файлах, где разрешены допустимые JS/TS, а не только внутри Vue SFC.
Поскольку макросы доступны глобально, их типы должны быть явно указаны (например, в файле env.d.ts
):
ts
/// <reference types="vue/macros-global" />
При явном импорте макросов из vue/macros
этот тип будет работать без объявления глобальных переменных.
Явное согласие
Предупреждение
Следующее описание относится только к версии Vue 3.3 и ниже. Поддержка ядра будет удалена в версии 3.4 и выше. Если вы собираетесь продолжать использовать трансформацию, перейдите на Vue Macros.
Vite
- Требуется
@vitejs/plugin-vue@>=2.0.0
- Применяется к файлам SFC и js(x)/ts(x). Перед применением преобразования для файлов выполняется быстрая проверка использования, поэтому для файлов, не использующих макросы, не должна снижаться производительность. — Обратите внимание, что
reactivityTransform
теперь является опцией корневого уровня плагина, а не вложенной какscript.refSugar
, поскольку она влияет не только на SFC.
js
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
vue-cli
- В настоящее время влияет только на SFC.
- Требуется
vue-loader@>=17.0.0
js
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
Обычный webpack
+ vue-loader
- В настоящее время влияет только на SFC.
- Требуется
vue-loader@>=17.0.0
js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
reactivityTransform: true
}
}
]
}
}