1. 背景最近仕事でVue.jsを使ったPJの保守・運用をする必要が生まれた React.jsは数年間触ってきたがVueは初めてである そのためでReactと比較した学習ログを残す 2. Reactとの違いどちらもVDOMのライブラリ 大きな違いは次になるReactはJSX、Vueはディレクティブの記法 Reactはhooks(イミュータブル)で、VueはComposition API(ミュータブル) Reactは何度も関数が実行される、VueはSetupが一度だけ実行される React:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React , { useState } from 'react' ;
function App() {
const [ message , setMessage ] = useState ( 'Hello, React!' );
return (
< div >
< p >{ message }</ p >
< button onClick = {() => setMessage ( 'Updated message' )}>
Update Message
</ button >
</ div >
);
}
export default App ;
Copy Vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
< template >
< div >
< p >{{ message }}</ p >
< button @click ="updateMessage" > Update Message </ button >
</ div >
</ template >
< script >
import { ref } from 'vue' ;
export default {
setup () {
const message = ref ( 'Hello, Vue!' );
const updateMessage = () => {
message . value = 'Updated message' ;
};
return {
message ,
updateMessage
};
}
};
</ script >
Copy 3. ディレクティブ3.1. v-textいわゆるタグの中身を表示する。
1
2
3
< span v-text ="msg" ></ span >
<!-- same as -- >
< span >{{ msg }}</ span >
Copy 3.2. v-bindいわゆるpropsを渡す記法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- 属性をバインドする -->
< img v -bind:src ="imageSrc" />
<!-- 動的な属性名 -->
< button v -bind:[key] ="value" ></ button >
<!-- 省略記法 -->
< img :src ="imageSrc" />
<!-- 同名省略記法 ( 3.4 + ) , : src = "src" のように展開する -->
< img : src />
<!-- 動的な属性名の省略記法 -->
< button :[key] ="value" ></ button >
<!-- インラインの文字列連結 -->
< img : src = "'/path/to/images/' + fileName" />
<!-- クラスのバインド -->
< div : class = "{ red: isRed }" ></ div >
< div : class = "[classA, classB]" ></ div >
< div : class = "[classA, { classB: isB, classC: isC }]" ></ div >
<!-- スタイルのバインド -->
< div : style = "{ fontSize: size + 'px' }" ></ div >
< div : style = "[styleObjectA, styleObjectB]" ></ div >
<!-- 属性のオブジェクトをバインド -->
< div v-bind ="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- props のバインド。"prop" は子コンポーネントで宣言する必要がある -->
<MyComponent :prop="someThing" />
<!-- 親の props を子コンポーネントと共有するために渡す -->
<MyComponent v-bind="$props" />
<!-- XLink -->
<svg><a :xlink :special ="foo" ></ a ></ svg >
Copy 3.3. v-else-ifif文。
1
2
3
4
5
6
7
8
9
10
11
12
< div v-if ="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'" >
C
</ div >
< div v-else >
Not A / B / C
</ div >
Copy 3.4. v-forテンプレートブロックを複数回レンダリングする記法。
1
2
3
4
5
6
7
8
9
10
11
< div v-for ="item in items">
{{ item.text }}
</div>
<div v-for="(item, index) in items"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, name, index) in object"></div>
<div v-for="item in items" :key="item.id" >
{{ item.text }}
</ div >
Copy 3.5. v-modelフォーム入力要素またはコンポーネントに双方向バインディングする記法 <input>
, <select>
, <textarea>
のみ対応3.6. v-onイベントリスナーを定義できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- メソッドハンドラー -->
< button v -on:click ="doThis" ></ button >
<!-- 動的イベント -->
< button v -on:[event] ="doThis" ></ button >
<!-- インラインステートメント -->
< button v -on : click = "doThat('hello', $event)" ></ button >
<!-- 省略記法 -->
< button @click ="doThis" ></ button >
<!-- 動的イベントの省略記法 -- >
< button @[event] ="doThis" ></ button >
<!-- stop propagation -->
< button @click.stop ="doThis" ></ button >
<!-- prevent default -->
< button @click.prevent ="doThis" ></ button >
<!-- 式なしで prevent default -->
< form @ submit.prevent ></ form >
<!-- 修飾子の連鎖 -->
< button @click.stop.prevent ="doThis" ></ button >
<!-- キーのエイリアスを用いたキー修飾子 -->
< input @keyup.enter ="onEnter" />
<!-- 一度だけトリガーされるクリックイベント -->
< button v -on:click.once ="doThis" ></ button >
<!-- オブジェクト構文 -->
< button v-on ="{ mousedown: doThis, mouseup: doThat }" ></ button >
Copy 4. SFC単一ファイルコンポーネントの略 vue拡張子で終わり、HTML、CSS と JavaScriptをモジュール化したもの 例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
< script setup >
import { ref } from 'vue'
const greeting = ref ( 'Hello World!' )
</ script >
< template >
< p class = "greeting" >{{ greeting }}</ p >
</ template >
< style >
. greeting {
color : red ;
font - weight : bold ;
}
</ style >
Copy 5. Ref, Reactive, Watch, WatchEffect, Computed関数について5.1. Ref, Reactiveref, reactiveはReactでいうsetStateに近い。
ref:プリミティブ値(文字列、数値、ブール値など)やオブジェクト、配列などのリアクティブな参照を作成 valueを使って値にアクセス 単純なプリミティブ値や単一の変数をリアクティブにしたい場合 refでオブジェクトを扱う場合は階層が深い部分ではリアクティブにならない reactive:オブジェクトや配列をリアクティブにするために使用 通常のオブジェクトや配列のように直接プロパティにアクセス 複数のプロパティを持つオブジェクトや配列をリアクティブにしたい場合 ref:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { ref } from 'vue' ;
export default {
setup() {
const message = ref ( 'Hello, Vue 3!' );
const updateMessage = () => {
message . value = 'Updated message' ;
};
return {
message ,
updateMessage
};
}
};
Copy reactive:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { reactive } from 'vue' ;
export default {
setup() {
const state = reactive ({
message : 'Hello, Vue 3!'
});
const updateMessage = () => {
state . message = 'Updated message' ;
};
return {
state ,
updateMessage
};
}
};
Copy ただし、ReactはSetStateでSetterを利用して値を変えていたが、Vueはtemplateの中で直接状態のvalueにアクセスしてミュータブルに値を更新できる。
1
2
3
4
5
6
7
8
9
10
< template >
< p > You clicked {{ count }} times </ p >
< button @click ="count++" > Click me </ button >
</ template >
< script setup lang = "ts" >
import { ref } from "vue" ;
const count = ref ( 0 );
</ script >
Copy 5.2. Watch, WatchEffectwatchは値の変更監視で、ReactのuseEffectに近い。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ref , watch } from 'vue' ;
export default {
setup() {
const message = ref ( 0 );
watch ( message , ( newValue , oldValue ) => {
console . log ( `message changed from ${ oldValue } to ${ newValue } ` );
});
return {
message
};
}
};
Copy ただし、副作用のクリーンアップはWatchEffectを使う。
React:
1
2
3
4
5
useEffect (() => {
const controller = new AbortController ();
fetch ( url , { signal : controller.signal }). then (( resp ) => ...)
return () => controller . abort ();
}, [ url ]);
Copy Vue:
1
2
3
4
5
6
< script setup lang = "ts" >
watchEffect (( onCleanUp ) => {
const controller = new AbortController ();
onCleanUp (() => controller . abort ());
fetch ( url . value , { signal : controller . signal }). then ( resp => ...)
});
Copy 5.3. ComputedReactのHooksとComposition APIには次の違いがある。
Hooks状態をイミュータブル に保つことを強制 専用のsetter関数を使う Composition API値をrefやreactiveで覆うことで状態をミュータブル に更新するこができる weakMapやProxyを用いることでライブラリ側で効率的に依存関係を更新できる 故に、一見正しそうな、以下のvueのコードは間違い。inputlengthは入力しても更新はされない。
Reactでは状態が更新されるたびに(memo化していなければ)コンポーネントの再生成が行われる 他方、Vueはsetupは一度のみ呼ばれるので、状態はイミュータブルではない そのため、状態の変化を反映するにはriggerEffectを発火させなければならない それがcomputed
となる 下記コードはsetterの部分を次にすると、うまく動くconst inputLength = computed(() => input.value.length);
1
2
3
4
5
6
7
8
9
10
11
12
< template >
< div >
< input type = "text" v-model ="input" />
<div>{{ inputLength }}</div>
</div>
</template>
<script setup lang="ts" >
import { ref } from " vue ";
const input = ref ("");
const inputLength = input.value.length ;
</ script >
Copy 6. スロットslotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。
例:
次のように、<navigation-link>
の定義にslotを定義する。
1
2
3
4
5
6
7
< a
v -bind:href ="url"
class = "nav-link"
>
< font-awesome-icon name = "user" > < /font-awesome-icon>
< slot ></ slot >
</ a >
Copy その後次のように使用する。
1
2
3
< navigation-link url = "/profile" >
Your Profile
< /navigation-link>
Copy すると、innner contentsがslotと置き換わる仕組み。
1
2
3
4
< navigation-link url = "/profile" >
< font-awesome-icon name = "user" > < /font-awesome-icon>
Your Profile
< /navigation-link>
Copy 7. 糖衣構文7.1. setupvueの歴史的には、methodsとdataでtemplateに渡す値を決めていた。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
< script >
export default {
data () {
return {
count : 0
}
},
methods : {
increment () {
this . count ++
}
},
mounted () {
console . log ( this . count ) // 0
}
}
</ script >
< template >
< button @click ="increment" >{{ count }}</ button >
</ template >
Copy それが、vue3からはsetup関数でまとめるようになった。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
< script >
import { ref } from 'vue'
export default {
setup () {
const count = ref ( 0 )
// テンプレートや他の Options API フックを公開
return {
count
}
},
mounted () {
console . log ( this . count ) // 0
}
}
</ script >
< template >
< button @click ="count++" >{{ count }}</ button >
</ template >
Copy そのsetupがグルーバルスコープになったのが、script setupというシンタックスシュガー。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< script setup >
import { ref , onMounted } from 'vue'
const count = ref ( 0 )
const increment = () => {
count . value ++
}
onMounted (() => {
console . log ( count . value ) // 0
})
</ script >
< template >
< button @click ="increment" >{{ count }}</ button >
</ template >
Copy 7.2. scopedscopedをつけると、そのSFCのみでstyleが適用される。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
< template >
< div class = "container" >
< button @click ="increment" >{{ count }}</ button >
</ div >
</ template >
< script setup >
import { ref } from 'vue'
const count = ref ( 0 )
const increment = () => {
count . value ++
}
</ script >
< style scoped >
. container {
text - align : center ;
}
button {
background - color : blue ;
color : white ;
border : none ;
padding : 10 px ;
cursor : pointer ;
}
button : hover {
background - color : darkblue ;
}
</ style >
Copy 8. その他8.1. Proxyオブジェクトとはオブジェクトの基本操作(プロパティの設定や、値の列挙)を拡張してくれるオブジェクト 関数フッキング、またはAOPの一種 1
2
3
4
5
6
7
8
9
const animal = { neko : 'にゃん' , inu : 'わん' };
var proxy = new Proxy ( animal , {
get ( target , prop ) {
return prop in target ? target [ prop ] : 'そんなのいないよ' ;
}
});
console . log ( proxy . inu ); //わん
console . log ( proxy . helicopter ); //そんなのいないよ
Copy 8.2. MapとWeakMapの違いWeakMapキーはオブジェクトのみ キーは弱参照で保持されるため、他に参照がなくなるとガベージコレクションされる sizeプロパティは存在しない Mapキーは任意の値(オブジェクト、プリミティブ)を使用できる キーは強参照で保持される sizeプロパティがあり、現在のエントリ数を取得できる Map:
1
2
3
4
5
6
7
8
9
10
11
12
> a = new Map();
Map {}
> b = { hello: 11 };
{ hello: 11 }
> a.set(b, 1);
Map { { hello: 11 } => 1 }
> a.get(b)
1
> b = null;
null
> a.size
1
Copy WeakMap:
1
2
3
4
5
6
7
8
9
10
11
12
> a = new WeakMap();
WeakMap { <items unknown> }
> b = { hello: 11 };
{ hello: 11 }
> a.set(b, 1);
WeakMap { <items unknown> }
> a.get(b)
1
> b = null;
null
// この時点でaのbキーの値が削除されている
// また、weakmapにはsizeメソッドがない
Copy 8.3. WeakMapの使い道例
次のように、Elementを受け取り処理を行いそれを監視する場合 もし、SPAなどの場合はElementが何度も生成されてしまうため、消されたElementのゴミがMapに貯まる 故に、DOMエレメントのdeleteイベントにhookして、map変数からデータを削除する必要がある もし、完全にmapから不要なelのキーを削除しないと、メモリーリークとなってしまう weakmapを利用したら、keyがGCされたら、weakmapのKVも削除されるのでシンプルになる 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// NOTE: マップキーが文字列ではなく、オブジェクト
let map = new WeakMap ();
// 複数回呼ばれる
function execute ( elKey ){
doSomethingWith ( elKey );
// 既存の回数を取得
const called = ( map . get ( elKey ) || 0 ) + 1 ;
map . set ( elKey , called ); // 設定したが、GCにより自動的に削除される
// 10回目で通知
if ( called % 10 === 0 ) {
report ( elKey );
}
}
Copy 9. 参考文献