32
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

VueAdvent Calendar 2019

Day 4

Composition APIってなんだ

Last updated at Posted at 2019-12-03

これはVue Advent Calendar 2019 4日目の記事です。

主語がでかそうなんですが大した内容じゃないです、文字多め。Composition API(旧Function-based API)のrfcが出てからそこそこ時間が過ぎ、現在はvue-nextに取り込まれメンテされています。正式リリースは来年だそうです。

ところでそもそもComposition APIってなんなんでしょうか。なんで実装されるのか。ここら辺の認識をせっかくなのでしっかり合わせて正式リリース日を迎えましょうというのがこの記事の趣旨になります。

Composition APIとは何か

そんなに難しいものでもなく、そもそもしっかりREADMEにコンセプトは書かれています。※抜粋

Logic Reuse & Code Organization

  1. The code of complex components become harder to reason about as features grow over time. This happens particularly when developers are reading code they did not write themselves. The root cause is that Vue's existing API forces code organization by options, but in some cases it makes more sense to organize code by logical concerns.
  2. Lack of a clean and cost-free mechanism for extracting and reusing logic between multiple components. (More details in Logic Extraction and Reuse)

特に2つ目、「複数のコンポーネント間でロジックを抽出して再利用するための、クリーンでコストのかからないメカニズムの欠如」は、成長するVueアプリケーションにおいて致命的な問題になりえます。

現Option API(今の書き方)では、コンポーネントはロジックとviewを閉じ込めた単一のファイルであり、それ以上はどう頑張っても分解することができません。

これを解決しようとしたのがPresentational ComponentとContainer Componentのような設計手法です。見た目に関心を持つコンポーネントとアプリケーションの動作に関心を持つコンポーネントを分離していい感じにしようみたいな。

しかしこの設計はもっともテストをしたいContainer Componentの方がアプリケーションの成長に合わせて肥大化し、複雑になり、テスタビリティが失われてしまうという欠点があります。(設計の頑張り次第ではあります)

SFCという設計そのものがVueユーザーのニーズ、アプリケーションの規模について来られなくなったとも言えます。覚えている人も多いかと思いますがそもそもVueは当初から小〜中規模向けのような扱いをされていました。しかし、スモールスタートしたプロダクトが中規模、大規模と成長することは自然なことであり、誰もが到達しうるものです。

というわけで、現状のVueが抱える問題を解決するために生まれたのがComposition APIです。ミソは問題を解決するためにという部分で、既存の置き換えではないということです。不要な人には不要。

SFCからの脱出

Composition APIではもはやSFCという制約はなく、自由にロジックを抽出することができます。(ライフサイクルさえも!)

このコードはドキュメントの設計セクションにあるサンプルです。


export default {
  setup() { 
    // Network
    const { networkState } = useNetworkState()

    // Folder
    const { folders, currentFolderData } = useCurrentFolderData(networkState)
    const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
    const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
    const { showHiddenFolders } = useHiddenFolders()
    const createFolder = useCreateFolder(folderNavigation.openFolder)

    // Current working directory
    resetCwdOnLeave()
    const { updateOnCwdChanged } = useCwdUtils()

    // Utils
    const { slicePath } = usePathUtils()

    return {
      networkState,
      folders,
      currentFolderData,
      folderNavigation,
      favoriteFolders,
      toggleFavorite,
      showHiddenFolders,
      createFolder,
      updateOnCwdChanged,
      slicePath
    }
  }
}

function useCurrentFolderData(networkState) { // computedとかwatchとか中で使う
}

function useFolderNavigation({ networkState, currentFolderData }) { // ...
}

function useFavoriteFolder(currentFolderData) { // ...
}

function useHiddenFolders() { // ...
}

function useCreateFolder(openFolder) { // ...
}

ここではuseのプレフィックスがついた関数がそれぞれCompositionと呼ばれる関数になります。どの関数がリアクティブな値を扱うcomposition(合成関数)なのか判別するためにuseを用いることが推奨されています。多分。

setup()の中では呼び出す関数内でもライフサイクルメソッドや各種Vue APIを利用できます。これは外部ファイルにもできます。

つまり、

import { 
  useCurrentFolderData,
  useFolderNavigation,
  useFavoriteFolder,
  useHiddenFolders,
  useCreateFolder
} from '~/compositions/folders'

import { 
  useCwdUtils,
  usePathUtils
} from '~/compositions/utils'

export default {
  setup() { 
    // Network
    const { networkState } = useNetworkState()

    // Folder
    const { folders, currentFolderData } = useCurrentFolderData(networkState)
    const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
    const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
    const { showHiddenFolders } = useHiddenFolders()
    const createFolder = useCreateFolder(folderNavigation.openFolder)

    // Current working directory
    resetCwdOnLeave()
    const { updateOnCwdChanged } = useCwdUtils()

    // Utils
    const { slicePath } = usePathUtils()

    return {
      networkState,
      folders,
      currentFolderData,
      folderNavigation,
      favoriteFolders,
      toggleFavorite,
      showHiddenFolders,
      createFolder,
      updateOnCwdChanged,
      slicePath
    }
  }
}

こんな感じでまるごと外部ファイルとして抽出することもできます。setup()はいわばエントリポイントで、各種アプリケーションロジックを利用しtemplateへ値を返すだけの役割として機能させることができます。抽出した関数はjestのような純粋なテストルーツのみを利用してテストをすることができ、関数はJavaScriptユーザーが普段行っている粒度管理で構成できるため、テスタビリティの高い設計を目指すことができます。

viewとロジックが分離されたので、テストもレンダリングのみを確認するものとロジックのI/Oを見るものに分けることができます。

共通化が絶対ではない

基本的に共通化の文脈でComposition APIは語られますが、共通化が絶対というわけではなく単にテストがしやすく、複雑な状態管理をコンポーネントから追いやる目的の利用もありです。

共通化するということは複数の参照を持つということであり、依存が増えれば増えるほど影響範囲が分からなくなって手が付けられないという自体に陥ることもあります。mixinがわかりやすい例ですね。

共通化するものはアプリケーションコアとして提供して、それ以外は単一のコンポーネント、またはページ内のコンポーネント、Vuexの参照が同じページ同士での共通化に留める等、スコープを意識することが重要になりそうです。

おわりに

話が長くなってきたのでまとめるとこうです。

  • Composition APIはコンポーネントからロジックを抽出して管理できるAPIである
  • 中規模以上のVueアプリケーションにおいてぶつかる問題に対処するためのツールである

利用することで以下のような恩恵を受けられます。

  • Presentational Component/Container Componentを用いずにviewとアプリケーションロジックを分離できる
  • アプリケーションロジックのテストがより容易になる

さらに既存APIよりも優れた面がいくつかあります。

  • thisがない(人によってはデメリットになりうるかも?)
  • TypeScriptサポートの充実
  • フルで書いた場合ランタイムが小さくなる
  • JavaScript設計の知見がそのままVueアプリケーションの設計に生かせる

最後に開発者(Evan?)からのComposition APIに関するコメントです(Googel翻訳)※一部抜粋
https://vue-composition-api-rfc.netlify.com/#more-flexibility-requires-more-discipline

多くのユーザーが指摘したように、Composition APIはコード編成の柔軟性を高めますが、開発者が「正しく行う」ためにはより多くの規律が必要です。スパゲッティコードにつながることを心配する人もいます。つまり、Composition APIはコード品質の上限を引き上げる一方で、下限も引き下げます。

ある程度同意します。ただし、次のことを信じています。

1.上限のゲインは、下限のロスをはるかに上回ります。

2.適切なドキュメントとコミュニティガイダンスにより、コード編成の問題に効果的に対処できます。

充実したドキュメントもVueの良さの1つなので、期待して待ってます。

明日は@taaiさんです。

32
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?