Misskey & Webテクノロジー最前線

MisskeyにおけるVueのComposition APIの活用

本連載は分散型マイクロブログ用ソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説を行っています。

以前から紹介していますが、MisskeyはフロントエンドのUIフレームワークとしてVue 3を採用しており、そのVueの新しい機能であるComposition APIも活用しています。

今回はそのComposition APIについて解説します。

Composition APIとは

簡単に言うと、Vueのコンポーネントには以下の2通りの書き方があります。

  • Options APIを使った今までの書き方
  • Composition APIを使った新しい書き方

Composition APIのほうが新しいですが、現時点では従来のOptions APIが非推奨になったりはしておらず、Vueのドキュメントを見てもどちらも同じくらい「推して」いるようです。

(個人的な意見では、書き方が複数混在するのは混乱を招くため、古いOptions APIを早く廃止または非推奨にして、より柔軟に扱えるComposition APIに一本化したほうが良いように思いますが、互換性やユーザー層を考えるとなかなかそういうわけにもいかないのかもしれませんね。)

Composition APIを使用すると、関連する処理を一箇所にまとめて記述できるようになり、読みやすいコンポーネントが作成できるほか、ロジックを再利用可能な形に抽出してより柔軟にコンポーネントを書くことができます。

Misskeyではすべてのコンポーネントについて、Composition APIへの移行が完了しています。

Composition APIの例

複数のロジックをもつあるコンポーネントに、⁠1秒ごとにテキストを更新」というロジックを新たに追加することを考えましょう。

それを実現するには、コンポーネントがインスタンス化された時にwindow.setIntervalを呼び出してテキスト更新処理を登録し、コンポーネントが破棄された際にはwindow.clearIntervalを呼び出して更新処理を解除する、というようになります。

この一連の登録・解除の処理は、Composition APIを用いない場合(つまりOptions APIを用いた場合)以下のようになります。

export default {
  data() {
    return {
      text: null,
      intervalId: null,
    }
  },
  mounted() {
    this.intervalId = window.setInterval(this.changeText, 1000)
    
    // ... そのほかに必要な何らかの処理(1) ...
  },
  unmounted() {
    window.clearInterval(this.intervalId)
    
    // ... そのほかに必要な何らかの処理(2) ...
  },
  methods: {
    // ... そのほかに必要な何らかの処理(3) ...

    changeText() {
      this.text = getSomething()
    }
  }
}

登録処理・解除処理や更新ハンドラーの間に、関係のない別のロジックが挟まって記述されていて、視覚的に離ればなれになっているのがわかります。

それに対して、Composition APIを用いた場合以下のようになります。

const text = ref(null)
let intervalId = null

function changeText() {
  text.value = getSomething()
}

onMounted(() => {
  intervalId = window.setInterval(changeText, 1000)
})

onUnmounted(() => {
  window.clearInterval(intervalId)
})

// ... そのほかに必要な何らかの処理(1) ...
// ... そのほかに必要な何らかの処理(2) ...
// ... そのほかに必要な何らかの処理(3) ...

refというのはリアクティビティを実現するためのラッパーで、これによって値の変更がDOMにリアルタイムで反映されるようになります。実際の値は.valueで参照します。

注目してほしいのは、登録処理・解除処理や更新ハンドラー連続して記述されている点です。これにより、コードの見通しが良くなり、読みやすいコンポーネントを記述することが可能になっています。

さらにComposition APIを使用する場合、必ずしもロジックをコンポーネント内に書く必要がありません。

どういうことかというと、これら一連のロジックを別の関数に抽出することができ、その関数を別ファイルからimportする形にもできるということです。

export function useInterval(fn, interval) {
  let intervalId = null

  onMounted(() => {
    intervalId = window.setInterval(fn, interval)
  })

  onUnmounted(() => {
    window.clearInterval(intervalId)
  })
}

上記のファイルをコンポーネントでimportして使うようにすると、以下のようになります。

import { useInterval } from '...'

const text = ref(null)

useInterval(() => {
  text.value = getSomething()
}, 1000)

// ... そのほかに必要な何らかの処理(1) ...
// ... そのほかに必要な何らかの処理(2) ...
// ... そのほかに必要な何らかの処理(3) ...

このようにロジックを別ファイルに抽出することで、複数のコンポーネント間で共通するロジックを再利用することも可能になります。

また、このようにコンポーネントから抽出した関数は慣例的にuse〇〇という名前にされることが多く、汎用的な様々なuse〇〇が集められたライブラリも公開されています。

これらの「関連するロジックを一箇所にまとめて記述する」「ロジックを関数として抽出する」といった芸当は従来のOptions APIでは(ほぼ)不可能でした。

そしてこのようなAPIスタイルおよび設計思想は、Vueに限らず最近のフロントエンドフレームワークでのトレンドになっており、今後主流になっていくものと思われます。

マクロ(旧 Reactivity Transform)の利用

Composition APIに含まれるリアクティビティAPIは、マクロVue Macrosを使用するとより簡潔に書くことが可能です。

ノート以前はReactivity Transform(または ref sugar)という名前で、Vue 3標準で(実験的という位置付けでしたが)使えました。しかしその後、Reactivity TransformはVueコンポーネント内でしか使えないなどの理由から標準機能としては削除され、使用したい場合は外部ライブラリ(Vue Macros)という位置付けで利用する形になったという経緯があります。

この経緯はRFCに残されていますので、興味のある方は見てみてください。

マクロを使用すると例えば以下のように書けます。

let text = $ref(null)

function changeText() {
  text = getSomething()
}

リアクティブな変数を定義する際、refの代わりに$refが使われ、値を参照する際は.valueと記述する必要がなくなっています。

仕組みとしては、Vueのコンパイラが$refを見つけると、通常のrefを用いたコードに書き換えます。つまりマクロやシンタックスシュガーのようなもので、ランタイムで何か特別なことをするわけではありません。

ただし、前述したようにVueコンポーネント内でしか使えず、それ以外の場所での記述と一貫性がなくなるといったデメリットもありますので、必ずの使用が推奨されるものではありません。

Options APIの削除

プロジェクト内でComposition APIのみ使用する場合、Vueコンパイラの__VUE_OPTIONS_API__フラグをfalseにすることで、ランタイムから完全にOptions APIのためのコードを削除できます。

Viteなら以下のように設定します。

export default {
  // 中略
  define: {
    __VUE_OPTIONS_API__: false
  }
}

これによりバンドルサイズをいくらか削減できます。しかし、自分でOptions APIを使用していなくても依存しているライブラリ内でOptions APIが使われている部分が残っていたりする場合は、__VUE_OPTIONS_API__falseにできないので注意しましょう。

MisskeyのコードベースにおいてもOptions APIの使用箇所は残っていませんが、依存するライブラリがまだOptions APIを使っているため、バンドルからOptions APIを完全に削除するまでには至っていません。

Misskeyでの活用

MisskeyではすべてのコンポーネントについてComposition APIへの移行が完了しているのは先に述べた通りです。

ただそれだけではなく、Composition APIがコンポーネントの外でも使えるという利点を活かし、クライアント全体で使うステートの管理も行っています。

管理するステートにはアカウント情報やクライアントの設定情報などが含まれ、ステートをリアクティビティAPIのreactiveを使用することで、それらの情報の変更をリアルタイムでクライアント上に反映させることが可能です。

このようなステート管理は、Vue公式でpiniaといったライブラリが提供されていますが、Misskeyでは「レジストリ」という、サーバー側で様々な設定情報を管理するシステムが既に存在することなどから、現時点ではライブラリを使用せず自前で用意しています。

このほかにも、例で説明したようなuse関数も活用していて、Misskey固有のものでいうと「指定した要素にツールチップを表示させる」ものがあったりします。

まとめ

今回はVue 3から標準で使えるようになったComposition APIについて紹介しました。

Composition APIを使用することで、コンポーネントのリーダビリティが向上するほか、より柔軟にロジックを記述できることを説明しました。また、それらのロジックを関数として抽出し、複数のコンポーネント間で再利用可能になることも特徴に挙げました。

他にも、Composition APIはTree-shaking(ビルド時にコードの無駄な部分を削除する)との相性が良いなど、従来の書き方であるOptions APIに比べていくつものメリットがあります。

Options APIは現時点では続投されていますが、Composition APIの方が後発であり優れている点が多いことから、将来的には非推奨になることも十分予想できますので、まだOptions APIを使用している方は早めに移行することを推奨します。

おすすめ記事

記事・ニュース一覧