You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We can't do this directly using the code from the previous example but we'll come back to this example later to see how to adapt it to be compatible with Vue's reactivity system.
45
43
46
-
<divclass="reactivecontent">
47
-
<common-codepen-snippettitle="Proxies and Vue's Reactivity Explained Visually"slug="VwmxZXJ"tab="result"theme="light":height="500":team="false"user="sdras"name="Sarah Drasner":editable="false":preview="false" />
48
-
</div>
44
+
First, let's dig a bit deeper into how Vue implements the core reactivity requirements outlined above.
To be able to run our sum whenever the values change, the first thing we need to do is wrap it in a function:
53
49
54
50
```js
55
-
constdinner= {
56
-
meal:'tacos'
51
+
constupdateSum= () => {
52
+
sum = val1 + val2
57
53
}
54
+
```
58
55
59
-
consthandler= {
60
-
get(target, prop) {
61
-
return target[prop]
56
+
But how do we tell Vue about this function?
57
+
58
+
Vue keeps track of which function is currently running by using an *effect*. An effect is a wrapper around the function that initiates tracking just before the function is called. Vue knows which effect is running at any given point and can run it again when required.
59
+
60
+
To understand that better, let's try to implement something similar ourselves, without Vue, to see how it might work.
61
+
62
+
What we need is something that can wrap our sum, like this:
63
+
64
+
```js
65
+
createEffect(() => {
66
+
sum = val1 + val2
67
+
})
68
+
```
69
+
70
+
We need `createEffect` to keep track of when the sum is running. We might implement it something like this:
71
+
72
+
```js
73
+
// Maintain a stack of running effects
74
+
construnningEffects= []
75
+
76
+
constcreateEffect=fn=> {
77
+
// Wrap the passed fn in an effect function
78
+
consteffect= () => {
79
+
runningEffects.push(effect)
80
+
fn()
81
+
runningEffects.pop()
62
82
}
83
+
84
+
// Automatically run the effect immediately
85
+
effect()
63
86
}
87
+
```
64
88
65
-
constproxy=newProxy(dinner, handler)
66
-
console.log(proxy.meal)
89
+
When our effect is called it pushes itself onto the `runningEffects` array, before calling `fn`. Anything that needs to know which effect is currently running can check that array.
67
90
68
-
// tacos
69
-
```
91
+
Effects act as the starting point for many key features. For example, both component rendering and computed properties use effects internally. Any time something magically responds to data changes you can be pretty sure it has been wrapped in an effect.
92
+
93
+
While Vue's public API doesn't include any way to create an effect directly, it does expose a function called `watchEffect` that behaves a lot like the `createEffect` function from our example. We'll discuss that in more detail [later in the guide](/guide/reactivity-computed-watchers.html#watcheffect).
94
+
95
+
But knowing what code is running is just one part of the puzzle. How does Vue know what values the effect uses and how does it know when they change?
We can't track reassignments of local variables like those in our earlier examples, there's just no mechanism for doing that in JavaScript. What we can track are changes to object properties.
100
+
101
+
When we return a plain JavaScript object from a component's `data` function, Vue will wrap that object in a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) with handlers for `get` and `set`. Proxies were introduced in ES6 and allow Vue 3 to avoid some of the reactivity caveats that existed in earlier versions of Vue.
102
+
103
+
<divclass="reactivecontent">
104
+
<common-codepen-snippettitle="Proxies and Vue's Reactivity Explained Visually"slug="VwmxZXJ"tab="result"theme="light":height="500":editable="false":preview="false" />
105
+
</div>
106
+
107
+
That was rather quick and requires some knowledge of [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to understand! So let’s dive in a bit. There’s a lot of literature on Proxies, but what you really need to know is that a **Proxy is an object that encases another object and allows you to intercept any interactions with that object.**
108
+
109
+
We use it like this: `new Proxy(target, handler)`
72
110
73
111
```js
74
112
constdinner= {
75
113
meal:'tacos'
76
114
}
77
115
78
116
consthandler= {
79
-
get(target, prop) {
117
+
get(target, property) {
80
118
console.log('intercepted!')
81
-
return target[prop]
119
+
return target[property]
82
120
}
83
121
}
84
122
@@ -89,17 +127,19 @@ console.log(proxy.meal)
89
127
// tacos
90
128
```
91
129
130
+
Here we've intercepted attempts to read properties of the target object. A handler function like this is also known as a *trap*. There are many different types of trap available, each handling a different type of interaction.
131
+
92
132
コンソールログ以外にも、ここでは思い通りの操作が可能です。必要な場合は、実際の値を返さ _ない_ ようにすることさえできます。これにより、プロキシは API の作成において強力なものになっています。
One challenge with using a Proxy is the `this`binding. We'd like any methods to be bound to the Proxy, rather than the target object, so that we can intercept them too. Thankfully, ES6 introduced another new feature, called `Reflect`, that allows us to make this problem disappear with minimal effort:
95
135
96
136
```js{7}
97
137
const dinner = {
98
138
meal: 'tacos'
99
139
}
100
140
101
141
const handler = {
102
-
get(target, prop, receiver) {
142
+
get(target, property, receiver) {
103
143
return Reflect.get(...arguments)
104
144
}
105
145
}
@@ -110,16 +150,16 @@ console.log(proxy.meal)
110
150
// tacos
111
151
```
112
152
113
-
前述の通り、何らかの変更があった時に最終的な値を更新する API を実装するには、何らかの変更があった時に新しい値を設定する必要があるでしょう。この処理をハンドラー内の `track` という関数で、 `target`と `key` を引数として渡して行います。
153
+
The first step towards implementing reactivity with a Proxy is to track when a property is read. We do this in the handler, in a function called `track`, where we pass in the `target`and `property`:
The implementation of `track` isn't shown here. It will check which *effect* is currently running and record that alongside the `target` and `property`. This is how Vue knows that the property is a dependency of the effect.
174
+
175
+
Finally, we need to re-run the effect when the property value changes. For this we're going to need a `set` handler on our proxy:
The proxied object is invisible to the user, but under the hood it enables Vue to perform dependency-tracking and change-notification when properties are accessed or modified. One caveat is that console logging will format proxied objects differently, so you may want to install [vue-devtools](https://github.com/vuejs/vue-devtools) for a more inspection-friendly interface.
206
+
207
+
If we were to rewrite our original example using a component we might do it something like this:
208
+
209
+
```js
210
+
constvm=createApp({
211
+
data() {
212
+
return {
213
+
val1:2,
214
+
val2:3
215
+
}
216
+
},
217
+
computed: {
218
+
sum() {
219
+
returnthis.val1+this.val2
220
+
}
221
+
}
222
+
}).mount('#app')
223
+
224
+
console.log(vm.sum) // 5
225
+
226
+
vm.val1=3
227
+
228
+
console.log(vm.sum) // 6
229
+
```
230
+
231
+
The object returned by `data` will be wrapped in a reactive proxy and stored as `this.$data`. The properties `this.val1` and `this.val2` are aliases for `this.$data.val1` and `this.$data.val2` respectively, so they go through the same proxy.
232
+
233
+
Vue will wrap the function for `sum` in an effect. When we try to access `this.sum`, it will run that effect to calculate the value. The reactive proxy around `$data` will track that the properties `val1` and `val2` were read while that effect is running.
234
+
235
+
As of Vue 3, our reactivity is now available in a [separate package](https://github.com/vuejs/vue-next/tree/master/packages/reactivity). The function that wraps `$data` in a proxy is called [`reactive`](/api/basic-reactivity.html#reactive). We can call this directly ourselves, allowing us to wrap an object in a reactive proxy without needing to use a component:
236
+
237
+
```js
238
+
constproxy=reactive({
239
+
val1:2,
240
+
val2:3
241
+
})
242
+
```
243
+
244
+
We'll explore the functionality exposed by the reactivity package over the course of the next few pages of this guide. That includes functions like `reactive` and `watchEffect` that we've already met, as well as ways to use other reactivity features, such as `computed` and `watch`, without needing to create a component.
Note that Vue does not wrap primitive values such as numbers or strings in a Proxy, so you can still use `===` directly with those values:
210
292
211
-
<divclass="reactivecontent">
212
-
<common-codepen-snippettitle="Second Reactivity with Proxies in Vue 3 Explainer"slug="GRJZddR"tab="result"theme="light":height="500":team="false"user="sdras"name="Sarah Drasner":editable="false":preview="false" />
The template for a component is compiled down into a [`render`](/guide/render-function.html) function. The `render` function creates the [VNodes](/guide/render-function.html#the-virtual-dom-tree) that describe how the component should be rendered. It is wrapped in an effect, allowing Vue to track the properties that are 'touched' while it is running.
A `render` function is conceptually very similar to a `computed` property. Vue doesn't track exactly how dependencies are used, it only knows that they were used at some point while the function was running. If any of those properties subsequently changes, it will trigger the effect to run again, re-running the `render` function to generate new VNodes. These are then used to make the necessary changes to the DOM.
218
306
219
-
[//]: #'TODO: Insert diagram'
307
+
<divclass="reactivecontent">
308
+
<common-codepen-snippettitle="Second Reactivity with Proxies in Vue 3 Explainer"slug="GRJZddR"tab="result"theme="light":height="500":team="false"user="sdras"name="Sarah Drasner":editable="false":preview="false" />
0 commit comments