Реактивность в Vue
Опубликовано: 2022-03-10В этой статье мы рассмотрим реактивность в Vue, как она работает и как мы можем создавать реактивные переменные, используя вновь созданные методы и функции. Эта статья предназначена для разработчиков, которые хорошо понимают, как работает Vue 2.x, и хотят ознакомиться с новым Vue 3.
Мы собираемся создать простое приложение, чтобы лучше понять эту тему. Код этого приложения можно найти на GitHub.
По умолчанию JavaScript не является реактивным . Это означает, что если мы создадим переменную boy
и сошлемся на нее в части A нашего приложения, а затем продолжим изменять boy
в части B, часть A не будет обновляться новым значением boy
.
let framework = 'Vue'; let sentence = `${framework} is awesome`; console.log(sentence) // logs "Vue is awesome" framework = 'React'; console.log(sentence) //should log "React is awesome" if 'sentence' is reactive.
Фрагмент выше — прекрасный пример нереактивной природы JavaScript — поэтому изменение не отражается в переменной sentence
.
В Vue 2.x свойства props
, computed
и data()
по умолчанию были реактивными, за исключением свойств, которые отсутствуют в data
при создании таких компонентов. Это означает, что когда компонент внедряется в DOM, только существующие свойства в объекте data
компонента будут вызывать обновление компонента, если и когда такие свойства изменяются.
Внутри Vue 3 использует объект Proxy
(функция ECMAScript 6), чтобы гарантировать, что эти свойства являются реактивными, но по-прежнему предоставляет возможность использовать Object.defineProperty
из Vue 2 для поддержки Internet Explorer (ECMAScript 5). Этот метод определяет новое свойство непосредственно в объекте или изменяет существующее свойство объекта и возвращает объект.
На первый взгляд, поскольку большинство из нас уже знает, что реактивность не является чем-то новым в Vue, может показаться ненужным использование этих свойств, но API параметров имеет свои ограничения, когда вы имеете дело с большим приложением с многократно используемыми функциями в нескольких приложениях. части приложения. С этой целью был представлен новый Composition API, помогающий абстрагировать логику, чтобы упростить чтение и обслуживание базы кода. Кроме того, теперь мы можем легко сделать любую переменную реактивной независимо от ее типа данных, используя любые новые свойства и методы.
Когда мы используем параметр setup
, который служит точкой входа для Composition API, объект data
, computed
свойства и methods
недоступны, поскольку экземпляр компонента еще не создан при выполнении setup
. Это делает невозможным использование преимуществ встроенной реактивности любой из этих функций в setup
. В этом уроке мы узнаем обо всех способах, которыми мы можем это сделать.
Реактивный метод
Согласно документации, reactive
метод, эквивалентный Vue.observable()
в Vue 2.6, может быть полезен, когда мы пытаемся создать объект, все свойства которого являются реактивными (например, объект data
в параметрах). API). Под капотом объект data
в Options API использует этот метод, чтобы сделать все свойства в нем реактивными.
Но мы можем создать свой собственный реактивный объект следующим образом:
import { reactive } from 'vue' // reactive state let user = reactive({ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "[email protected]", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" }, "cars": { "number": 0 } })
Здесь мы импортировали reactive
метод из Vue, а затем объявили нашу user
переменную, передав ее значение этой функции в качестве аргумента. При этом мы сделали user
реактивным, и, таким образом, если мы используем user
в нашем шаблоне, и если объект или свойство этого объекта должны измениться, то это значение будет автоматически обновлено в этом шаблоне.
ref
Точно так же, как у нас есть метод для того, чтобы сделать объекты реактивными, нам также нужен метод, чтобы сделать реактивными другие автономные примитивные значения (строки, логические значения, неопределенные значения, числа и т. д.) и массивы. Во время разработки мы будем работать с этими другими типами данных, а также нуждаться в том, чтобы они были реактивными. Первый подход, о котором мы могли бы подумать, это использовать reactive
и передать значение переменной, которую мы хотим сделать реактивной.
import { reactive } from 'vue' const state = reactive({ users: [], });
Поскольку у reactive
есть глубокая реактивная конверсия, user
как свойство также будет реактивным, тем самым достигая нашей цели; следовательно, user
всегда будет обновлять везде, где он используется в шаблоне такого приложения. Но с помощью свойства ref
мы можем сделать любую переменную с любым типом данных реактивной, передав значение этой переменной в ref
. Этот метод также работает для объектов, но он вкладывает объект на один уровень глубже, чем при использовании reactive
метода.
let property = { rooms: '4 rooms', garage: true, swimmingPool: false } let reactiveProperty = ref(property) console.log(reactiveProperty) // prints { // value: {rooms: "4 rooms", garage: true, swimmingPool: false} // }
Под капотом ref
принимает переданный ей аргумент и преобразует его в объект с ключом value
. Это означает, что мы можем получить доступ к нашей переменной, вызвав variable.value
, и мы также можем изменить ее значение, вызвав ее таким же образом.
import {ref} from 'vue' let age = ref(1) console.log(age.value) //prints 1 age.value++ console.log(age.value) //prints 2
При этом мы можем импортировать ref
в наш компонент и создать реактивную переменную:
<template> <div class="home"> <form @click.prevent=""> <table> <tr> <th>Name</th> <th>Username</th> <th>email</th> <th>Edit Cars</th> <th>Cars</th> </tr> <tr v-for="user in users" :key="user.id"> <td>{{ user.name }}</td> <td>{{ user.username }}</td> <td>{{ user.email }}</td> <td> <input type="number" name="cars" v-model.number="user.cars.number" /> </td> <td> <cars-number :cars="user.cars" /> </td> </tr> </table> <p>Total number of cars: {{ getTotalCars }}</p> </form> </div> </template> <script> // @ is an alias to /src import carsNumber from "@/components/cars-number.vue"; import axios from "axios"; import { ref } from "vue"; export default { name: "Home", data() { return {}; }, setup() { let users = ref([]); const getUsers = async () => { let { data } = await axios({ url: "data.json", }); users.value = data; }; return { users, getUsers, }; }, components: { carsNumber, }, created() { this.getUsers(); }, computed: { getTotalCars() { let users = this.users; let totalCars = users.reduce(function(sum, elem) { return sum + elem.cars.number; }, 0); return totalCars; }, }; </script>
Здесь мы импортировали ref
, чтобы создать переменную реактивных users
в нашем компоненте. Затем мы импортировали axios
для получения данных из файла JSON в public
папке и импортировали наш компонент carsNumber
, который мы создадим позже. Следующее, что мы сделали, — это создали переменную реактивных users
с помощью метода ref
, чтобы users
могли обновляться всякий раз, когда изменяется ответ из нашего файла JSON.
Мы также создали функцию getUser
, которая извлекает массив users
из нашего JSON-файла с помощью axios, и мы присвоили значение из этого запроса переменной users
. Наконец, мы создали вычисляемое свойство, которое вычисляет общее количество автомобилей, имеющихся у наших пользователей, поскольку мы изменили его в разделе шаблона.
Важно отметить, что при доступе к свойствам ref
, которые возвращаются в разделе шаблона или за пределами setup()
, они автоматически разворачиваются неглубоко. Это означает, что refs
, которые являются объектами, по-прежнему требуют .value
для доступа. Поскольку users
— это массив, мы могли бы просто использовать users
а не users.value
в getTotalCars
.
В разделе шаблонов мы отобразили таблицу, в которой отображается информация о каждом пользователе, вместе с компонентом <cars-number />
. Этот компонент принимает свойство cars
, которое отображается в строке каждого пользователя как количество автомобилей, которые у него есть. Это значение обновляется всякий раз, когда значение cars
изменяется в пользовательском объекте , и именно так объект data
или computed
свойство будут работать, если мы будем работать с API параметров.
toRefs
Когда мы используем Composition API, функция setup
принимает два аргумента: props
и context
. Эти props
передаются от компонента в setup()
, и это позволяет получить доступ к реквизитам, которые есть у компонента, из этого нового API. Этот метод особенно полезен, потому что он позволяет деструктурировать объекты без потери их реактивности.
<template> <p>{{ cars.number }}</p> </template> <script> export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { console.log(props); // prints {gender: "female", cars: Proxy} }, }; </script> <style></style>
Чтобы использовать значение, которое является объектом из props
в Composition API, сохраняя при этом его реактивность, мы используем toRefs
. Этот метод берет реактивный объект и преобразует его в простой объект, в котором каждое свойство исходного реактивного объекта становится ref
. Это означает, что cars
поддерживают…
cars: { number: 0 }
… теперь станет следующим:
{ value: cars: { number: 0 }
Благодаря этому мы можем использовать cars
внутри любой части установочного API, сохраняя при этом его реактивность.
setup(props) { let { cars } = toRefs(props); console.log(cars.value); // prints {number: 0} },
Мы можем наблюдать за этой новой переменной с помощью watch
API-интерфейса композиции и реагировать на это изменение, как нам захочется.
setup(props) { let { cars } = toRefs(props); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }
toRef
Другой распространенный вариант использования, с которым мы можем столкнуться, — это передача значения , которое не обязательно является объектом, а скорее одним из типов данных, которые работают с ref
(массив, число, строка, логическое значение и т. д.). С помощью toRef
мы можем создать реактивное свойство (т.е. ref
) из исходного реактивного объекта. Это гарантирует, что свойство останется реактивным и будет обновляться при каждом изменении родительского источника.
const cars = reactive({ Toyota: 1, Honda: 0 }) const NumberOfHondas = toRef(state, 'Honda') NumberOfHondas.value++ console.log(state.Honda) // 1 state.Honda++ console.log(NumberOfHondas.value) // 2
Здесь мы создали реактивный объект reactive
методом со свойствами Toyota
и Honda
. Мы также использовали toRef
для создания реактивной переменной из Honda
. Из приведенного выше примера видно, что когда мы обновляем Honda
, используя либо объект реактивных cars
, либо NumberOfHondas
, значение обновляется в обоих случаях.
Этот метод похож и все же сильно отличается от метода toRefs
, который мы рассмотрели выше, в том смысле, что он поддерживает соединение со своим источником и может использоваться для строк, массивов и чисел. В отличие от toRefs
, нам не нужно беспокоиться о существовании свойства в его источнике во время создания, потому что, если это свойство не существует во время создания этой ref
и вместо этого возвращает null
, оно все равно будет сохранено. в качестве допустимого свойства с установленной формой watcher
, чтобы при изменении этого значения эта ref
, созданная с использованием toRef
, также обновлялась.
Мы также можем использовать этот метод для создания реактивного свойства из props
. Это будет выглядеть так:
<template> <p>{{ cars.number }}</p> </template> <script> import { watch, toRefs, toRef } from "vue"; export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { let { cars } = toRefs(props); let gender = toRef(props, "gender"); console.log(gender.value); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }, }; </script>
Здесь мы создали ref
, которая будет основываться на свойствах gender
, полученных из props
. Это удобно, когда мы хотим выполнить дополнительные операции над реквизитом определенного компонента.
Заключение
В этой статье мы рассмотрели, как работает реактивность в Vue, используя некоторые из недавно представленных методов и функций из Vue 3. Мы начали с рассмотрения того, что такое реактивность и как Vue использует объект Proxy
за кулисами для достижения этого. Мы также рассмотрели, как мы можем создавать реактивные объекты, используя reactive
, и как создавать реактивные свойства, используя ref
.
Наконец, мы рассмотрели, как преобразовать реактивные объекты в простые объекты, каждое из свойств которых является ref
, указывающей на соответствующее свойство исходного объекта, и мы увидели, как создать ref
для свойства в реактивном исходном объекте.
Дополнительные ресурсы
- «Прокси» (объект), MDN Web Docs
- «Основы реактивности», Vue.js
- «Ссылки», Vue.js
- «Регистрация хука жизненного цикла внутри
setup
», Vue.js