Vue3の仕組み
許 志偉
こんにちは、Airレジのバックエンド(Java、Kotlin)を開発しているキョ シイです。
Vue3はまだリリースされていないですが、公開された設計思想から先に内部の構造や仕組みなどを解析したいと思います。
Vue3のdata binding
方法はObject.defineProperty
を代わりに、ES6の特性に基づき、Proxy
を使っています。
Vue3は前のVue2バージョンアップデートよりほぼ根本的にTypescript
で書き換えられたものです。
Vue2に比べて、Vue3は以下の特徴があります:
- もっと早い、もっと軽量的に
- 中身はTypeScriptで全部書き換えました
- function-based apiなどの新機能の追加
今回はProxy
をベースにして、Vue3のミニ版を実現してみました。
開発は、TDD(Test Driven Development)という開発手法を使って、Vue3の簡単な機能を開発してきました。
なぜTDD
- 品質を保証できる、回帰テストをしやすい、開発効率を向上できます。
- test case はもはや仕様書として扱えます。
- トップレベルの設計後(製品設計と技術設計を含む)、ボトムからプログラミングを開始できます。
今回はkarma
をテストフレームワークとして使いました。テストケースは下記通りです。
1
2
3
4
5
6
7
8
9
10
11
12
describe('Proxy test', function() {
it('should proxy vm._data.a = vm.a', function() {
const vm = new Vue({
data() {
return {
a: 2
}
}
})
expect(vm.a).toEqual(2)
})
})
npm test
を実行すれば、テスト結果を見えます。
Proxyとは
Proxyオブジェクトは別のオブジェクトをラップし、 プロパティやその他の読み取り/書き込みなどの操作をインターセプトします。 必要に応じてそれらを独自に処理できるようにします。 つまりこの特性を生かして、値の変更を検知できます。Vue3はこう実現しています。
例えば:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const vm = {
a: 1
}
const proxy = new Proxy(vm, {
get (target, key) {
console.log('you will get value of key : ' + key)
return vm[key] + 1
},
set (target, key, value) {
console.log('you will set value of key : ' + key)
vm[key] = value
return true
}
})
// you will get value of key : a
// 2
console.log(proxy.a)
// you will set value of key : a
proxy.a = 2
例はvm
の代理オブジェクト(proxy)を生成し、vm中のプロパティの変動や削除などを検知できました。
プロパティ変動の通知(Watcher)
プロパティが定義された場合に、Vue3はハンドラーを追加してくれます。 プロパティが変わったら、Vue3はハンドリングを行ってくれます。
状態変更の自動通知と言えば、Observerパターンを使うのは一般的です。 まずは簡単なSubjectを定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { remove } from './util'
export default class Dep {
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
removeSub (sub) {
remove(this.subs, sub)
}
notify (payload) {
this.subs.forEach(sub => {
sub.update(payload)
})
}
}
data
やprops
に定義されたプロパティのkeyとそれに関連するWatcherをdepsに保存します。
1
2
3
4
5
6
7
// propsやdataの中に定義されたプロパティ(key)とそれに関連するWatcherをdepsに保存する
$watch (key, cb) {
if (!this.deps[key]) {
this.deps[key] = new Dep()
}
this.deps[key].addSub(new Watcher(this.proxy, key, cb))
}
プロパティの値などが更新された場合、通知メソットもinvokeされます。
1
2
3
4
notifyChange (key, pre, val) {
const dep = this.deps[key]
dep && dep.notify({ pre, val })
}
virtual Dom vs shadow Dom
プロパティの値などが変更された場合、Vue3はvirtual Domを通じて、変更が必要となるDomだけを変更します。
本実践では、virtual Domまで実現していません。なぜかといいますと、今後shadow Dom
を使う可能性が高いことです。
参考URL:
https://develoger.com/shadow-dom-virtual-dom-889bf78ce701
https://svelte.dev/blog/virtual-dom-is-pure-overhead