Skip to content

Commit 27a580f

Browse files
committed
1 parent 69974bc commit 27a580f

File tree

1 file changed

+147
-57
lines changed

1 file changed

+147
-57
lines changed

src/guide/reactivity.md

Lines changed: 147 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,67 +18,105 @@
1818
JavaScript は通常このように機能しません。JavaScript で同等のものを書こうとしたら次のようになります:
1919

2020
```js
21-
var val1 = 2
22-
var val2 = 3
23-
var sum = val1 + val2
21+
let val1 = 2
22+
let val2 = 3
23+
let sum = val1 + val2
2424

25-
// sum
26-
// 5
25+
console.log(sum) // 5
2726

2827
val1 = 3
2928

30-
// sum
31-
// 5
29+
console.log(sum) // Still 5
3230
```
3331

3432
最初の値を更新しても、合計値は調整されません。
3533

3634
では、 JavaScript を使って以下の要素をどうやって実現するのでしょうか。
3735

38-
- いずれかの値に変化があった時に検出する
39-
- それを変更する関数を追跡する
40-
- 最終的な値を更新できるように関数を発火させる
36+
As a high-level overview, there are a few things we need to be able to do:
4137

42-
## Vue がこれらの変更を追跡する方法
38+
1. **Track when a value is read.** e.g. `val1 + val2` reads both `val1` and `val2`.
39+
2. **Detect when a value changes.** e.g. When we assign `val1 = 3`.
40+
3. **Re-run the code that read the value originally.** e.g. Run `sum = val1 + val2` again to update the value of `sum`.
4341

44-
プレーンな JavaScript オブジェクトを `data` オプションとしてアプリケーションまたはコンポーネントインスタンスに渡すと、Vue はそのすべてのプロパティを走査して、ゲッターとセッターのハンドラを使用しそれらを[プロキシ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy)に変換します。 これは ES6 のみの機能ですが、旧式の `Object.defineProperty` を使用した Vue 3 のバージョンを IE ブラウザをサポートするために提供しています。どちらも表面的には同じ API を提供しますが、プロキシバージョンの方がよりスリムで、パフォーマンスが改良されています。
42+
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.
4543

46-
<div class="reactivecontent">
47-
<common-codepen-snippet title="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.
4945

50-
この例はかなり素早いので、理解するには[プロキシ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy)についての知識がある程度必要です!では、少し詳しく見ていきましょう。プロキシに関する文献はたくさんありますが、本当に知っておく必要があることは **プロキシは別のオブジェクトまたは関数を包み、操作を差し込むこと(intercept)ができるオブジェクトだということです。**
46+
## How Vue Knows What Code Is Running
5147

52-
proxy は次のように使用します: `new Proxy(target, handler)`
48+
To be able to run our sum whenever the values change, the first thing we need to do is wrap it in a function:
5349

5450
```js
55-
const dinner = {
56-
meal: 'tacos'
51+
const updateSum = () => {
52+
sum = val1 + val2
5753
}
54+
```
5855

59-
const handler = {
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+
const runningEffects = []
75+
76+
const createEffect = fn => {
77+
// Wrap the passed fn in an effect function
78+
const effect = () => {
79+
runningEffects.push(effect)
80+
fn()
81+
runningEffects.pop()
6282
}
83+
84+
// Automatically run the effect immediately
85+
effect()
6386
}
87+
```
6488

65-
const proxy = new Proxy(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.
6790

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?
96+
97+
## How Vue Tracks These Changes
7098

71-
今のところは、オブジェクトをラップしてそれをそのまま返すだけです。かっこいいですが、まだ役に立つ物ではありません。しかしこれを見てください。プロキシでラップしている中で、このオブジェクトに操作を差し込むこともできます。この操作の差し込みはトラップと呼ばれています。
99+
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+
<div class="reactivecontent">
104+
<common-codepen-snippet title="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)`
72110

73111
```js
74112
const dinner = {
75113
meal: 'tacos'
76114
}
77115

78116
const handler = {
79-
get(target, prop) {
117+
get(target, property) {
80118
console.log('intercepted!')
81-
return target[prop]
119+
return target[property]
82120
}
83121
}
84122

@@ -89,17 +127,19 @@ console.log(proxy.meal)
89127
// tacos
90128
```
91129

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+
92132
コンソールログ以外にも、ここでは思い通りの操作が可能です。必要な場合は、実際の値を返さ _ない_ ようにすることさえできます。これにより、プロキシは API の作成において強力なものになっています。
93133

94-
さらに、プロキシは別の機能も提供してくれます。`target[prop]` のような値をただ返すだけではなく、これをさらに一歩進めて `this` のバインディングを適切に行うことができる `Reflect` と呼ばれる機能を使用することができます。これは次のようになります。
134+
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:
95135

96136
```js{7}
97137
const dinner = {
98138
meal: 'tacos'
99139
}
100140
101141
const handler = {
102-
get(target, prop, receiver) {
142+
get(target, property, receiver) {
103143
return Reflect.get(...arguments)
104144
}
105145
}
@@ -110,16 +150,16 @@ console.log(proxy.meal)
110150
// tacos
111151
```
112152

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`:
114154

115155
```js{7}
116156
const dinner = {
117157
meal: 'tacos'
118158
}
119159
120160
const handler = {
121-
get(target, prop, receiver) {
122-
track(target, prop)
161+
get(target, property, receiver) {
162+
track(target, property)
123163
return Reflect.get(...arguments)
124164
}
125165
}
@@ -130,20 +170,22 @@ console.log(proxy.meal)
130170
// tacos
131171
```
132172

133-
最後に、何らかの変更があった時に新しい値を設定します。このために、これらの変更を発火させることで、新しいプロキシに変更をセットします。
173+
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:
134176

135177
```js
136178
const dinner = {
137179
meal: 'tacos'
138180
}
139181

140182
const handler = {
141-
get(target, prop, receiver) {
142-
track(target, prop)
183+
get(target, property, receiver) {
184+
track(target, property)
143185
return Reflect.get(...arguments)
144186
},
145-
set(target, key, value, receiver) {
146-
trigger(target, key)
187+
set(target, property, value, receiver) {
188+
trigger(target, property)
147189
return Reflect.set(...arguments)
148190
}
149191
}
@@ -154,26 +196,66 @@ console.log(proxy.meal)
154196
// tacos
155197
```
156198

157-
数段落前のこのリストを覚えていますか?これで Vue がこれらの変更を処理する方法に対するいくつかの回答が出揃いました。
199+
Remember this list from earlier? Now we have some answers to how Vue implements these key steps:
158200

159-
- <strike>いずれかの値に変化があった時に検出する</strike>: プロキシがそれに対する操作の差し込みを可能にしているため、その必要がなくなりました
160-
- **それを変更する関数を追跡する**: これは、 `track` と呼ばれるプロキシ内のゲッターで行います
161-
- **最終的な値を更新できるように関数を発火させる**: `trigger` と呼ばれるプロキシ内のセッターで行います
201+
1. **Track when a value is read**: the `track` function in the proxy's `get` handler records the property and the current effect.
202+
2. **Detect when that value changes**: the `set` handler is called on the proxy.
203+
3. **Re-run the code that read the value originally:** the `trigger` function looks up which effects depend on the property and runs them.
162204

163-
プロキシされたオブジェクトはユーザーには見えませんが、内部的にはプロパティがアクセスまたは変更されたときに、Vue が依存関係の追跡と変更通知を実行できるようになっています。 Vue 3 以降、リアクティブは[個別のパッケージ](https://github.com/vuejs/vue-next/tree/master/packages/reactivity)で利用できるようになりました。注意点の 1 つは、変換されたデータオブジェクトがログに記録された時は、ブラウザコンソールが違った整形をすることです。そのため、 [vue-devtools](https://github.com/vuejs/vue-devtools) をインストールして、より見やすいインターフェイスにすることをお勧めします。
205+
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+
const vm = createApp({
211+
data() {
212+
return {
213+
val1: 2,
214+
val2: 3
215+
}
216+
},
217+
computed: {
218+
sum() {
219+
return this.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+
const proxy = 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.
164245

165246
## プロキシされたオブジェクト
166247

167248
Vue はリアクティブに作られたすべてのオブジェクトを内部的に追跡するため、常に同じオブジェクトに対して同じプロキシを返します。
168249

169250
ネストされたオブジェクトがリアクティブプロキシからアクセスされると、次のようにそのオブジェクト __ 返却される前にプロキシに変換されます:
170251

171-
```js
252+
```js{6-7}
172253
const handler = {
173-
get(target, prop, receiver) {
174-
track(target, prop)
254+
get(target, property, receiver) {
255+
track(target, property)
175256
const value = Reflect.get(...arguments)
176257
if (isObject(value)) {
258+
// Wrap the nested object in its own reactive proxy
177259
return reactive(value)
178260
} else {
179261
return value
@@ -194,28 +276,36 @@ const wrapped = new Proxy(obj, handlers)
194276
console.log(obj === wrapped) // false
195277
```
196278

197-
オリジナルとラップされたバージョンはほとんどの場合同じように動作しますが、 `.filter()``.map()` などの強力な同一性比較に依存する操作は失敗することに注意してください。オプション API を使用する場合、この注意点に出くわすことはほとんどありません。すべてのリアクティブな状態が `this` からアクセスされ、すでにプロキシだということが保証されているためです。
279+
Other operations that rely on strict equality comparisons can also be impacted, such as `.includes()` or `.indexOf()`.
198280

199-
しかし、コンポジション API を使用して明示的にリアクティブオブジェクトを作成する場合、元の生のオブジェクトへの参照を保持せず、次のようにリアクティブバージョンでのみ処理をすることがベストプラクティスです:
281+
The best practice here is to never hold a reference to the original raw object and only work with the reactive version:
200282

201283
```js
202284
const obj = reactive({
203285
count: 0
204286
}) // no reference to original
205287
```
206288

207-
## ウォッチャ
289+
This ensures that both equality comparisons and reactivity behave as expected.
208290

209-
すべてのコンポーネントインスタンスには対応するウォッチャインスタンスがあり、コンポーネントのレンダリング中に「触れられた」プロパティを依存関係として記録します。後に依存関係にあるもののセッターが発火されると、ウォッチャーに通知され、コンポーネントが再レンダリングされます。
291+
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:
210292

211-
<div class="reactivecontent">
212-
<common-codepen-snippet title="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" />
213-
</div>
293+
```js
294+
const obj = reactive({
295+
count: 0
296+
})
297+
298+
console.log(obj.count === 0) // true
299+
```
300+
301+
## How Rendering Reacts to Changes
214302

215-
オブジェクトをデータとしてコンポーネントインスタンスに渡すと、Vue はそれをプロキシに変換します。このプロキシにより、Vue はプロパティがアクセスまたは変更されたときに、依存関係の追跡と変更通知の実行ができるようになります。各プロパティは依存関係と見なされます。
303+
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.
216304

217-
最初のレンダリングの後、コンポーネントはレンダリング中にアクセスしたプロパティを依存関係一覧として追跡します。逆に言えば、コンポーネントはこれらの各プロパティの値を監視する購読者になります。プロキシがセット処理を傍受すると、プロパティは購読されているすべてのコンポーネントに再レンダリングを通知します。
305+
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.
218306

219-
[//]: # 'TODO: Insert diagram'
307+
<div class="reactivecontent">
308+
<common-codepen-snippet title="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" />
309+
</div>
220310

221311
> Vue 2.x 以前を使用している場合は、それらのバージョンに存在する変更検出の注意点に興味があるかもしれません[詳細はこちらをご覧ください](change-detection.md)

0 commit comments

Comments
 (0)