Реактивность в 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