🌱

Vue 3の開発を半年経験してみて学んだこと、感じたこと

2023/02/08に公開

はじめに

株式会社HajimariのITプロパートナーズ事業部でテックリードをしています。
野澤です。

今まで自分はLaravelを使ったMPAの開発案件に携わることが多かったのですが、
今回はVue 3を使ったSPAの開発プロジェクトに携わらせて頂いて、半年(正確には9ヶ月ほど)経ったので学んだこと、感じたことをアウトプットしていきます。

Piniaとの付き合い方の難しさ

  • pages以下のコンポーネントがレイアウトなど上の概念に影響を与えたいとき(例えば今いるページのメニュー名を太字にしたいとか。)
  • 兄弟コンポーネントが直接もう片方がの兄弟コンポーネントに影響を与えたい場合(親を経由しないで)
  • SPAのページ遷移で情報を引き継ぎたい場合
  • 各コンポーネントから参照される可能性が高い情報(ユーザーのログインしているかどうかとか、ユーザー名とか、アプリとして今どういう常態か)を管理したい時
  • そのコンポーネントから見せたくない状態を管理する時

が主なユースケースなのかと思っています。

ただ、例えばあるページにアクセスした瞬間に、そのページに存在するすべてのコンポーネントを初期化する情報を一旦storeに入れてしまって、その後各コンポーネントがそのstoreを参照、操作するのも一定ありな場面もあるのかもしない、、、

もっと手軽にStore使っちゃいけないのか?、、、
などなどまだ迷いはありますし、どういうふうに付き合っていくのが正解なのかまだ明確には見えてないです。。。

v-ifの値が変わるタイミング、:keyの値が変わるタイミングで再レンダリングされる

Vueはミュータブルな指向が強いと思っていて、再レンダリングは最小に済んでいるイメージです。
状態が変わってもそこには状態が変わったコンポーネントがあるイメージです。

一方Reactはイミュータブルな指向が強いと思っていて、状態が変わったらその状態を持っている新たなコンポーネントが都度作られるイメージです。
(状態が変わるとコンポーネントも変わる)

ですので、強制的に再レンダリングされる方法を知っていて役に立つ場面が多々ありました。
(ここ、再レンダリングされてほしいのに!という場面多々ありました。)

(再レンダリングまでいかなくても、データの初期化したいなというときはデータの初期化メソッドで切り出しておき、onBeforeUpdateでそれをよんであげるという方法でも実現できるかと思います。)

Composablesな関数はscript setup外の場所で呼ばれるとundefinedを返してしまう

タイトルどおりなのですが、これを知らずに苦労しました。
何でこれundefinedになってしまうんだ。。。という場面に多々遭遇しました。

https://vuejs.org/guide/reusability/composables.html#conventions-and-best-practices
↑詳しくはこちらの【Usage Restrictions】の章に記載されています。

Composablesな関数はscript setup内で呼び出すようにしましょう(当たり前)

子コンポーネントから親コンポーネントのデータを変更するときはpropsではなくemit使った方が良さそう

子コンポーネントはemitを利用してイベントを発行するだけ、実際の挙動は親コンポーネントが決める がきれいな形だと思いました。

propsで親コンポーネントのデータを変更するメソッドを渡し、子コンポーネントが実行することもできるのですが、それでは意図が薄れてしまう気がしてます。
(あえてemitを使って表現したほうが良いと思っています。)
(親コンポーネントのデータを変更しないけど、親が子の挙動を決めてあげたいときにはpropsでメソッドを渡すはありだと思っています。)

script setupでそのコンポーネントがどんなコンポーネントなのか認識しやすい記述の順番

<script setup lang="ts">
// props, emits句
const props = defineProps<{
	nyan: number
}>();
const emits = defineEmits<{
	(e: 'hoge'): void
}>();

// use句(piniaやcomposableなど)
const xxxStore = useXXXStore();

// reactive句
const count = ref(0);
const hoge = reactive({ data: 'hoge' });

// computed句
const fugafuga = computed(() => count * 2);

// watch(宣言した変数の真下に書いておくのが良さそう)
const num = ref(0)
watch(num, (next, prev) => {
  console.log(num);
} )

// このコンポーネント内で利用するfunction
function hoge(){}

// イベントハンドラで利用するfunction(メソッド名はonから始める)
function onClick(){}

// Lifecycle句
onBeforeMount(() => {});
</script>

このような順番でvueファイルに記述していくと、
ぱっとみてそのコンポーネントがどのような挙動をするのか把握しやすいです。

constでメソッド定義するよりfunctionの方がしっかりとメソッドを表現できる

const init = async () => {
    // 初期化処理
};

よりも

async function init(){
    // 初期化処理
}

の方がぱっとみてメソッドだと認識しやすいと思っています。
(constでメソッドを表現すると同じ名前のメソッド名をつけられないようになるというメリットはあるのですが、そもそもコンポーネントで分ける限り、このメリットもあんまり享受できないと思っているのでfunctionで良いなと思っています。。。)

watchEffectはあんまり使わない方が良い感覚

watchEffectを使うと、どのタイミングでコードが実行されるのかがとても追いづらくなる感覚があります。
あと予期しないタイミングで実行されたり。。。
(こんなときにもこのコード実行されてるんだ。。。が増える感覚)

watchはまだ監視対象の値を指定するので、コードが実行されるタイミングをwatchEffectよりは追いやすいと思うのですが、watchも多用するときつくなりそうな気がしています。

現状watchに関しては親コンポーネントから渡されたpropsが変更されるタイミングを監視する以外で使うのは良くなさそうな気がしています。

基本computedを利用し、それでも実現できない場合はwatchを使うという方針が自分的にはしっくりきています。

もし効果的なwatchの使い方、watchEffectの使い方を知っている方いましたらコメントで教えていただけると嬉しいです。

最後に

半年間のプロジェクトの中で自分が感じた事であったり、現場で学んだことをまとめてみました。
もし何か記事に間違えがあったり、アドバイスがあったりしたらコメントいただけると嬉しいです。

ここまで記事を読んでいただきありがとうございました。

Hajimari Tech Media

Discussion