反応性
React.jsと同様に、Vueはリアクティブです。つまり、アプリケーションの状態に対するすべての変更が自動的にDOMに反映されます。ただし、Reactとは異なり、Vueはレンダリング時に依存関係を追跡し、「比較」なしで関連部分のみを更新します。
Vue.jsの反応性の鍵はメソッド
Object.defineProperty
です。これにより、オブジェクトフィールドでカスタムのgetter / setterメソッドを指定し、そのフィールドへのすべてのアクセスをインターセプトできます。
const obj = {a: 1};
Object.defineProperty(obj, 'a', {
get() { return 42; },
set(val) { console.log('you want to set "a" to', val); }
});
console.log(obj.a); // prints '42'
obj.a = 100; // prints 'you want to set "a" to 100'
これにより、特定のプロパティがいつアクセスされているか、またはいつ変更されているかを判断し、プロパティが変更された後にすべての依存式を再評価できます。
式
Vue.jsを使用すると、ディレクティブを使用してJavaScript式をDOMノード属性にバインドできます。たとえば
<div v-text="s.toUpperCase()"></div>
、div内のテキストを大文字の変数値に設定します
s
。
などの文字列を評価する最も簡単な方法
s.toUpperCase()
は、を使用すること
eval()
です。evalは安全なソリューションとは見なされていませんでしたが、関数でラップしてカスタムグローバルコンテキストを渡すことで、少し改善することができます。
const call = (expr, ctx) =>
new Function(`with(this){${`return ${expr}`}}`).bind(ctx)();
call('2+3', null); // returns 5
call('a+1', {a:42}); // returns 43
call('s.toUpperCase()', {s:'hello'}); // returns "HELLO"
これはネイティブの
eval
ものよりも少し安全であり、 私たちが構築している単純なフレームワークには十分です。
プロキシ
これで、を使用
Object.defineProperty
してデータオブジェクトの各プロパティをラップでき ます。
call()
任意の式を評価し、式が直接または間接的にアクセスしたプロパティを判別するために使用できます 。また、変数の1つが変更されたため、式をいつ再評価する必要があるかを判断できる必要があります。
const data = {a: 1, b: 2, c: 3, d: 'foo'}; // Data model
const vars = {}; // List of variables used by expression
// Wrap data fields into a proxy that monitors all access
for (const name in data) {
let prop = data[name];
Object.defineProperty(data, name, {
get() {
vars[name] = true; // variable has been accessed
return prop;
},
set(val) {
prop = val;
if (vars[name]) {
console.log('Re-evaluate:', name, 'changed');
}
}
});
}
// Call our expression
call('(a+c)*2', data);
console.log(vars); // {"a": true, "c": true} -- these two variables have been accessed
data.a = 5; // Prints "Re-evaluate: a changed"
data.b = 7; // Prints nothing, this variable does not affect the expression
data.c = 11; // Prints "Re-evaluate: c changed"
data.d = 13; // Prints nothing.
指令
これで、任意の式を評価し、特定のデータ変数が変更されたときに評価する式を追跡できます。残っているのは、DOMノードの特定のプロパティに式を割り当て、データが変更されたときに実際にそれらを変更することだけです。
Vue.jsと同様に、
q-on:click
イベントハンドラーの
q-text
バインド、textContentの
q-bind:style
バインド、CSSスタイルのバインド などの特別な属性を使用 します。 「q」は「vue」に似ているため、ここでは「q-」プレフィックスを使用します。
サポートされている可能性のあるディレクティブのリストの一部を次に示します。
const directives = {
// Bind innerText to an expression value
text: (el, _, val, ctx) => (el.innerText = call(val, ctx)),
// Bind event listener
on: (el, name, val, ctx) => (el[`on${name}`] = () => call(val, ctx)),
// Bind node attribute to an expression value
bind: (el, name, value, ctx) => el.setAttribute(name, call(value, ctx)),
};
各ディレクティブは、DOMノードを受け取る関数であり、
q-on:click
(名前は「クリック」になります)などの場合のオプションのパラメーター名です 。また
value
、式のコンテキストとして使用する式文字列()とデータオブジェクトも必要 です。
すべてのビルディングブロックができたので、今度はすべてを接着します。
最終結果
const call = .... // Our "safe" expression evaluator
const directives = .... // Our supported directives
// Currently evaluated directive, proxy uses it as a dependency
// of the individual variables accessed during directive evaluation
let $dep;
// A function to iterate over DOM node and its child nodes, scanning all
// attributes and binding them as directives if needed
const walk = (node, q) => {
// Iterate node attributes
for (const {name, value} of node.attributes) {
if (name.startsWith('q-')) {
const [directive, event] = name.substring(2).split(':');
const d = directives[directive];
// Set $dep to re-evaluate this directive
$dep = () => d(node, event, value, q);
// Evaluate directive for the first time
$dep();
// And clear $dep after we are done
$dep = undefined;
}
}
// Walk through child nodes
for (const child of node.children) {
walk(child, q);
}
};
// Proxy uses Object.defineProperty to intercept access to
// all `q` data object properties.
const proxy = q => {
const deps = {}; // Dependent directives of the given data object
for (const name in q) {
deps[name] = []; // Dependent directives of the given property
let prop = q[name];
Object.defineProperty(q, name, {
get() {
if ($dep) {
// Property has been accessed.
// Add current directive to the dependency list.
deps[name].push($dep);
}
return prop;
},
set(value) { prop = value; },
});
}
return q;
};
// Main entry point: apply data object "q" to the DOM tree at root "el".
const Q = (el, q) => walk(el, proxy(q));
最高のリアクティブなVue.jsのようなフレームワーク。それはどれくらい役に立ちますか?次に例を示します。
<div id="counter">
<button q-on:click="clicks++">Click me</button>
<button q-on:click="clicks=0">Reset</button>
<p q-text="`Clicked ${clicks} times`"></p>
</div>
Q(counter, {clicks: 0});
1つのボタンを押すと、カウンターがインクリメントされ、コンテンツが自動的に更新されます
<p>
。別のものをクリックすると、カウンターがゼロに設定され、テキストも更新されます。
ご覧のとおり、Vue.jsは一見魔法のように見えますが、内部は非常にシンプルで、基本的な機能はほんの数行のコードで実装できます。
さらなるステップ
Vue.jsについて詳しく知りたい場合は、「q-if」を実装して式に基づいて要素の表示を切り替えるか、「q-each」を実装して重複する子のリストをバインドしてみてください(これは良い演習になります) )。
Q nanoframeworkの完全なソースは、 Githubにあります。問題を見つけたり、改善を提案したい場合は、遠慮なく寄付してください。
結論として、私は言及すべき
Object.defineProperty
ヴュー2 Vueの3で使用したとクリエイターが、すなわち、ES6設けられた他の施設に切り替えている
Proxy
と
Reflect
..。プロキシを使用すると、例のようにオブジェクトプロパティへのアクセスをインターセプトするハンドラーを渡すことができますが、リフレクトを使用すると、プロキシ内からオブジェクトプロパティにアクセスして、
this
オブジェクトをそのまま保持できます (definePropertyを使用した例とは異なります)。
私はプロキシとリフレクトの両方を読者の練習問題として残しているので、Qでそれらを正しく使用するように要求した人は誰でも、それ を組み合わせて喜んでいます。がんばろう!
あなたが記事を楽しんだことを望みます。ニュースをフォローし、Github、 Twitterで提案を共有するか、rssを介してサブスクライブ できます 。