Posted at
Day 23

Vue.jsのレンダリングがどうなってるか調べてみたけど、、、


背景

id, classも振らずにどうやってレンダリングしてるんだろ?と思い、どんな実装になってるのか調べてみた。jQueryなどでDOM操作してた感覚からすると不思議だったので。

Vueのバージョンは2.5.17になります。

<!DOCTYPE html>

<script src="./vue2.5.17.js"></script>

<div id="app"></div>

<script>
const vm = new Vue({
template: '<p>{{ msg }}</p>',
data: { msg: 'hello world!' }
}).$mount('#app')
</script>

このように実装してみて、defineReactiveにブレークポイント設定してみると以下の順に処理されてることがわかった。


Vue

function Vue (options) {

// options = {template: "<p>{{ msg }}</p>", data: {…}}
...
this._init(options);
}


_init

Vue.prototype._init = function (options) {

// vm = Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
var vm = this;
...
initState(vm);
...
};


initState

function initState (vm) {

...
// opts = {components: {…}, directives: {…}, filters: {…}, _base: ƒ, template: "<p>{{ msg }}</p>", …}
var opts = vm.$options;
...
if (opts.data) {
initData(vm);
}
...
}


initData

function initData (vm) {

// data = {msg: "hello world!", __ob__: Observer}
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
...
observe(data, true /* asRootData */);
}


observe

function observe (value, asRootData) {

...
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// value = {msg: "hello world!", __ob__: Observer}
ob = new Observer(value);
}
...
return ob
}


Observer

var Observer = function Observer (value) {

this.value = value;
...
if (Array.isArray(value)) {
...
} else {
// value = {msg: "hello world!", __ob__: Observer}
this.walk(value);
}
};


walk

Observer.prototype.walk = function walk (obj) {

// obj = {msg: "hello world!", __ob__: Observer}
var keys = Object.keys(obj); // keys = ["msg"]
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};


defineReactive

function defineReactive (

obj, // obj = {msg: "hello world!", __ob__: Observer}
key, // key = "msg"
val,
customSetter,
shallow
) {
var dep = new Dep(); // dep = Dep {id: 3, subs: Array(0)}
// property = {value: "hello world!", writable: true, enumerable: true, configurable: true}, obj = {msg: "hello world!", __ob__: Observer}, key = "msg"
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}

// cater for pre-defined getter/setters
var getter = property && property.get;
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;

var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}


defineReactivemsgにゲッター・セッターを持たせてるようです。

そこで、

vm.msg = 'こんにちは世界!'

とすると、defineReactiveで設定したセッターが反応して


defineReactive

dep.notify();


によりウォッチャに通知が行くみたいです。そこでウォッチャがレンダリング関数を呼び出してレンダリングされるようですが、結局このレンダリング関数を読み解かないとidclassなしでレンダリングできてる不思議は解決しなさそうだなと思ったところで力尽きました。

この記事は千 Advent Calendar 2018の23日目の予定でしたが、過ぎた上に調べきれてなくすみません。

近々続きを書きたいと思います。