<div id="app"> <div>Price: ${{ price }}</div> <div>Total: ${{ price*quantity }}</div> <div>Taxes: ${{ totalPriceWithTax }}</div> </div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> var vm = new Vue({ el: '#app', data: { price: 5.00, quantity: 2 }, computed: { totalPriceWithTax() { return this.price * this.quantity * 1.03 } } }) </script>
price
changes, the engine needs to perform three actions:price
value on the webpage.price
is multiplied by quantity
, and output the resulting value to the page.totalPriceWithTax
and, again, put what it returns on the page.price
changes, and how the engine tracks what happens on the page. What can be observed here is not similar to the work of a regular JS application. let price = 5 let quantity = 2 let total = price * quantity // 10 price = 20; console.log(`total is ${total}`)
10
.total
recalculated when the price
or quantity
variables change. That is, if the system of reactivity were applied when executing the above described code, not 10, but 40 would be output to the console:price
would not bring the number 40 to the console. In order for the total
figure to be recalculated when the price
or quantity
changes, we will need to create a reactivity system on our own and thereby achieve the behavior we need. The path to this goal we will break into several small steps.total
indicator is calculated, which will allow us to perform its recalculation when the price
or quantity
variable values change.price
or quantity
indicators have changed, you will need to call the saved code to recalculate the total
. It looks like this:total
, and also create a mechanism for storing functions that we may need later. let price = 5 let quantity = 2 let total = 0 let target = null target = function () { total = price * quantity } record() // , target() //
target
variable, and then call the record
function. We will talk about it below. I would also like to note that the target
function, using the syntax of ES6 arrow functions, can be rewritten as follows: target = () => { total = price * quantity }
record
function and the data structure used to store the functions: let storage = [] // target function record () { // target = () => { total = price * quantity } storage.push(target) }
record
function, we store the target
function (in our case, it is { total = price * quantity }
) in the storage
array, which allows us to call this function later, possibly using the replay
function, the code of which is shown below. This will allow us to call all the functions stored in storage
. function replay () { storage.forEach(run => run()) }
storage
array and execute each of them. price = 20 console.log(total) // 10 replay() console.log(total) // 40
let price = 5 let quantity = 2 let total = 0 let target = null let storage = [] function record () { storage.push(target) } function replay () { storage.forEach(run => run()) } target = () => { total = price * quantity } record() target() price = 20 console.log(total) // 10 replay() console.log(total) // 40
target
variable, and which receives notifications in case we need to re-execute these functions. class Dep { // Dep - Dependency constructor () { this.subscribers = [] // , // notify() } depend () { // record if (target && !this.subscribers.includes(target)){ // target // this.subscribers.push(target) } } notify () { // replay this.subscribers.forEach(sub => sub()) // - } }
storage
array, we now store our anonymous functions in the subscribers
array. Instead of the record
function, the depend
method is now called. Also here, instead of the replay
function, the notify
function is notify
. Here's how to run our code using the Dep
class: const dep = new Dep() let price = 5 let quantity = 2 let total = 0 let target = () => { total = price * quantity } dep.depend() // target target() // total console.log(total) // 10 - price = 20 console.log(total) // 10 - , dep.notify() // - console.log(total) // 40 -
target
.Dep
object for each variable. In addition, it would be good to encapsulate the behavior of creating anonymous functions somewhere, which should be called when updating the relevant data. Perhaps this will help us an additional function, which we call watcher
. This will lead us to replace this function from the previous example with a new function: let target = () => { total = price * quantity } dep.depend() target()
watcher
function, replacing this code, will look like this: watcher(() => { total = price * quantity })
watcher
function, the code of which is presented below, we can perform several simple actions: function watcher(myFunc) { target = myFunc // target myFunc dep.depend() // target target() // target = null // target }
watcher
function accepts, as an argument, the myFunc
function, writes it to the global target
variable, calls dep.depend()
to add this function to the list of subscribers, calls this function and resets the target
variable. price = 20 console.log(total) dep.notify() console.log(total)
target
as a global variable, instead of passing this variable to our functions, if necessary. We have good reason to do just that, you will understand later.Dep
. What if we need each of our variables to have its own class object Dep
? Before we continue, let's move the data we are working with into object properties: let data = { price: 5, quantity: 2 }
price
and quantity
) has its own internal object of class Dep
.watcher
function like this: watcher(() => { total = data.price * data.quantity })
data.price
property, we need the Dep
object of the price
property to put the anonymous function (stored in the target
) in its subscriber array (by calling dep.depend()
). In addition, since we also work with data.quantity
, we need the object Dep
quantity
property to put an anonymous function (again, stored in the target
) into its array of subscribers.data.price
property, then the corresponding anonymous function should only go into the repository of the Dep
object of this property.dep.notify()
for functions that are subscribed to price
property changes? This will be needed when changing the price
. This means that when our example is completely ready, the following code should work for us.price
or quantity
). This will allow, when this happens, to store the target
function in an array of subscribers, and, when the corresponding variable changes, to execute the function stored in this array. let data = { price: 5, quantity: 2 } Object.defineProperty(data, 'price', { // price get() { // console.log(`I was accessed`) }, set(newVal) { // console.log(`I was changed`) } }) data.price // data.price = 20 //
internalValue
, which we will use to store the current price
value. let data = { price: 5, quantity: 2 } let internalValue = data.price // Object.defineProperty(data, 'price', { // price get() { // console.log(`Getting price: ${internalValue}`) return internalValue }, set(newVal) { console.log(`Setting price to: ${newVal}`) internalValue = newVal } }) total = data.price * data.quantity // data.price = 20 //
data
object with getters and setters. Here we will use the Object.keys()
method, which returns an array of keys of the object passed to it. let data = { price: 5, quantity: 2 } Object.keys(data).forEach(key => { // data let internalValue = data[key] Object.defineProperty(data, key, { get() { console.log(`Getting ${key}: ${internalValue}`) return internalValue }, set(newVal) { console.log(`Setting ${key} to: ${newVal}`) internalValue = newVal } }) }) let total = data.price * data.quantity data.price = 20
data
object have getters and setters. This is what will appear in the console after running this code.total = data.price * data.quantity
and it retrieves the value of the price
property, we need the price
property to “remember” the corresponding anonymous function ( target
in our case). As a result, if the price
property is changed, that is, it is set to a new value, this will result in calling this function to repeat the operations performed by it, since it knows that a certain line of code depends on it. As a result, operations performed in getters and setters can be thought of as follows:Dep
class you already know in this description, you get the following:dep.depend()
is called to save the current target
function.dep.notify()
is called to rerun all saved functions. let data = { price: 5, quantity: 2 } let target = null // - , class Dep { constructor () { this.subscribers = [] } depend () { if (target && !this.subscribers.includes(target)){ this.subscribers.push(target) } } notify () { this.subscribers.forEach(sub => sub()) } } // , // Object.keys(data).forEach(key => { let internalValue = data[key] // // Dep const dep = new Dep() Object.defineProperty(data, key, { get() { dep.depend() // target return internalValue }, set(newVal) { internalValue = newVal dep.notify() // } }) }) // watcher dep.depend(), // function watcher(myFunc){ target = myFunc target() target = null } watcher(() => { data.total = data.price * data.quantity })
price
and quantity
properties have become reactive! All code that is responsible for forming a total
, when the price
or quantity
changes, is re-executed.
is written, containing getters and setters? Now he should be familiar to you. Each instance of the component has an instance of the observer method (blue circle), which collects dependencies on getters (red line). When, later, the setter is called, it notifies the observer method, which causes the component to be re-rendered. Here is the same scheme, provided with explanations relating it to our development.Dep
class that collects functions using the depend
method, and, if necessary, calls them again using the notify
method.watcher
function that allows you to control the code that we run (this is the target
function) that you may need to save in a Dep
object.Object.defineProperty()
method to create getters and setters.Source: https://habr.com/ru/post/418633/
All Articles