【Jetpack Compose】React HooksとSWRに学ぶ宣言的データフェッチのすすめ

## TL;DR

[React SWR](https://swr.vercel.app/ja)をJetpack Compose向けに移植して公開したぞ！

https://github.com/KazaKago/swr-compose

## 前置き

もし今からモダンなAndroidアプリをゼロから書こうと思ったらおそらく[Jetpack Compose](https://developer.android.com/jetpack/compose)を採用したいという人が多いかと思います、いわゆる宣言的UIと呼ばれるやつですね。
![https://developer.android.com/static/codelabs/jetpack-compose-animation/img/5bb2e531a22c7de0_1920.png?hl=ja](https://developer.android.com/static/codelabs/jetpack-compose-animation/img/5bb2e531a22c7de0_1920.png?hl=ja)
この宣言的と呼ばれるトレンドはUIだけに適用されているものなのでしょうか？
AndroidというプラットフォームだけでみてもCompose以外で宣言的に書ける部分は年々広がってきています。

例えば遷移先の画面から値を受け取る`startActivityForResult()`+`onActivityResult()`は非推奨になり、代わりに[registerForActivityResult()を宣言](https://developer.android.com/training/basics/intents/result?hl=ja)する形に置き換わりつつあるのは周知の通りです。
また、画面のライフサイクルのハンドリングはActivityの`onStart()`や`onResume()`のオーバーライドではなく、[LifecycleObserver()を宣言](https://developer.android.com/topic/libraries/architecture/lifecycle?hl=ja)する形が推奨されてきています。

### ComposeにおけるAAC ViewModelの立ち位置

では、この宣言的という文脈でデータの取得や状態管理に目を向けるとどうでしょうか？
今日だと[Android Architecture Components(=AAC)のViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel)を用いて状態管理やロジックの記述、データ取得を行うことが多いと思います。
現に[状態と Jetpack Compose](https://developer.android.com/jetpack/compose/state)のドキュメントにもAAC ViewModelが登場します。
![https://developer.android.com/static/images/jetpack/compose/state-dependencies.svg](https://developer.android.com/static/images/jetpack/compose/state-dependencies.svg)
しかしながらドキュメントにはこのようにも書かれています。

> 注: 実際のユースケースで ViewModel のメリットを活用できない場合や、別の方法で行う場合は、ViewModel の役割をプレーンな状態ホルダーのクラスに移行します。

> キーポイント: ViewModel は、特定の責任を持つ状態ホルダーの実装の詳細にすぎません。プロジェクトのモジュールを Android の依存関係から切り離したい場合は、インターフェースを利用して、さまざまなコンテキストで実装を切り替えられるようにします。

![スクリーンショット 2022-12-07 17.14.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129872/d26efcb1-e0aa-891f-bfdb-69e65ac5ef7b.png)
現状はAAC ViewModelがデファクトスタンダードになっているのでJetpack Composeと併用されることも多いですが、[状態ホルダー](https://developer.android.com/jetpack/compose/state?hl=ja#managing-state)としての役割が満たせれば本来何でも良いのです。
AAC ViewModel自体、状態の保持やライフサイクルに癖のあるAndroid Viewにおいて状態をより簡単に管理するために誕生したため、内部では命令的な記述をすることが一般的だと思います。
ゆえにLiveDataやStateFlowなどのリアクティブな機構を除き、データ取得や状態管理などのロジック周りに関しては（少なくとも私の観測範囲では）宣言的に書く流れはあまり見られません。

また、AAC ViewModel自身もライフサイクルがあり、スコープを持ち、なおかつステートフルな存在なので画面をまたいで再利用しにくい側面があることも否めません。
Jetpack Composeと違い、Androidというプラットフォームに依存しその影響を強く受けているものという点も扱いの難しさに拍車をかけています。
AAC ViewModelはAndroid Viewとの相互運用性の高さからも引き続き使われていくとは思いますが、フルJetpackCompose環境においては必ずしもベストな選択肢とは限らないのです。

もちろん、かつてAndroid View環境下においてAAC ViewModelの登場により解決されたことは非常に多く今でも素晴らしいものだとは思っていますが、Jetpack Compose環境ではより宣言的で適した状態ホルダーの代替が登場しても良いのではないでしょうか。

## ComposeがReactから受けた影響

Jetpack Composeが大きな影響を受けたであろうフレームワークの一つが[React](https://ja.reactjs.org/)です。[^1]
Webフロントはさほど詳しくない私の主観ですが、宣言的UIとして先駆者であるReactはJetpack Composeに比べエコシステムが成熟している面が多数あり、今後ComposeがReactの辿った道をある程度なぞる可能性は大いにあると思っています。（もちろん後発であるComposeのほうが洗練されている部分もたくさんあります）
![スクリーンショット 2022-12-07 17.56.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129872/51f3312b-65dc-a6a8-5a9e-b1b845d79f6f.png)
その上でReactにおける状態管理のトレンドを見てみると、少し前は[Redux](https://redux.js.org/)や[Flux](https://github.com/facebook/flux)などに代表される単一方向データフローが流行りましたが最近は[React Hooks](https://ja.reactjs.org/docs/hooks-intro.html)の登場で状況が変わってきているように思います。
Composeにも近い概念は取り入れられていて、例えばReact Hooksの基本機能の一つである `useState()` はComposeだと　`remember {}`で表現されており、例えば下記のコードは全く同じ振る舞いをします。

```js:React
function Example() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
```

```kotlin: Compose
@Composable
fun Example() {
    val (count, setCount) = remember { mutableStateOf(0) }
    Column {
        Text("You clicked $count times")
        Button(onClick = { setCount(count + 1) }) {
            Text("Click me")
        }
    }
}
```

Composeの`remember {}`がReact Hooksをベースに輸入されたものかはわかりませんが、近い思想と実装を持っていることは感じ取れると思います。
もし仮に`remember {}`がなかったとしたら、それこそViewModelを作ってそちらに値を保持することになっていたでしょう。

React Hooksでは `useState()` などのフック関数をラップして[独自フック](https://ja.reactjs.org/docs/hooks-custom.html)として再利用性を高めた上で宣言的に活用するケースも増えてきています。
対してCompose界隈ではReact Hooksのように宣言的に機能をまとめて関数を作るケースは（私が観測できていないだけかも知れませんが）アプリケーション開発の現場ではあまり普及していていない印象です。
しかしながら今後ViewModelを作って状態を置いたりロジックを書く代わりに、独自フック的なものを作るのが一般的になっていく可能性はあると思っています。

### 余談: Composeで独自フックを作ってみる

簡単な例ですが「画面の現在のライフサイクルの状態を監視・取得する」独自フックをComposeで作ってみます。

```kotlin:Compose
@Composable
fun rememberLifecycleState(): Lifecycle.State {
    var currentState by remember { mutableStateOf(Lifecycle.State.INITIALIZED) }
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val lifecycleObserver = LifecycleEventObserver { _, event ->
            currentState = event.targetState
        }
        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
    return currentState
}
```

ここまで作ってしまえば、使う側からはこの関数を呼び出すだけで常に最新のstateを取得できます。

```kotlin:Compose
@Composable
fun MainScreen() {
    val state = rememberLifecycleState()
    ...
}
```

ロジックを集約しフック関数内に隠蔽することで使う側からより簡単に利用できるのがフック関数の威力です。
前述したとおり、ライフサイクルイベントが`onResume()`や`onStart()`のオーバーライドではなく`LifecycleEventObserver`を使って宣言的に受け取れることになったことで、このような独自フックの実現がますます容易になっています。

## React SWR

そんなReact Hooksをフル活用して宣言的にデータ取得まで担ってしまおうぜ！という思想で誕生したのが[SWR](https://swr.vercel.app/ja)や[TanStack Query](https://tanstack.com/query)といった存在です。
いずれもReact Hooksをデータフェッチに活用したライブラリですが、今回はどちらかというとシンプルで軽量なSWRにフォーカスを当ててみます。
![スクリーンショット 2022-12-07 19.04.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129872/92b4580e-59d6-a00f-1e5a-b2931c3363cc.png)

https://github.com/vercel/swr

[SWRのドキュメント](https://swr.vercel.app/ja/docs)は全編通して日本語翻訳されていてめちゃくちゃ読みやすいのでとりあえず読んでくれい！でも十分なんですが、簡単に説明すると
データ取得に関する一般的な要件や問題に対して、こういうのあったらいいよね〜と思う機能をこれでもかというくらいに実装して `useSWR()` というフック関数一つにすべて押し込んだよくばりセットみたいなやつです（雑）

### SWRの思想

公式ドキュメントの引用そのまんまですが、SWRは下記の戦略に従っています。

> “SWR” という名前は、 [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861) で提唱された HTTP キャッシュ無効化戦略である stale-while-revalidate に由来しています。 SWR は、まずキャッシュからデータを返し（stale）、次にフェッチリクエストを送り（revalidate）、最後に最新のデータを持ってくるという戦略です。

この思想自体は革新的というわけではありませんが、画面上に表示される内容をできる限り最新の状態に更新しつつも、キャッシュがあればそれを先に表示させてUXを落とさないようにしているというわけです。

### SWRの基本的な使い方

こちらも公式ドキュメントそのままですがミニマムなSWRの利用方法は下記です。

```js:React
import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}
```

SWRの基本はキーバリューによるキャッシング機構です。
useSWR関数は下記の3つの引数を取ります。

- **key** ... リクエストするユニークな文字列
- **fetcher** ... （任意）第一引数に渡したURLを引数に取るfetch関数
- **options** ... （任意）SWRのオプション

keyはデータキャッシュのキーとしての役割も兼ねています。
そしてこの関数は下記を返却します。

- **data** ... fetcherによって取得したデータ
- **error** ... fetcherによってthrowされたエラー
- **isLoading** ... リクエストの初回読み込み中であるか否かのBool値
- **isValidating** ... リクエストまたは再検証の読み込みがあるか否かのBool値
- **mutate** ... キャッシュされたデータを更新する関数

このとき、dataの状態は
取得が完了していない場合はundefined
取得が完了した場合はその内容
となり、dataがundefinedか否かで読み込み中かどうかをUIに伝えることが出来ます。
ちなみにisLoadingは`typeof data === 'undefined' && !error`と等価です。[^2]

シンプルなインターフェースですがとても合理的です。

### よくSWRで便利と言われる機能

SWRが便利と言われる所以は下記のようなデータを取得する場合に一般的にほしいと言われる機能の大部分が網羅されているところにあります。

- [エラー発生時の効率的な自動再実行](https://swr.vercel.app/ja/docs/error-handling)
- [ウィンドウフォーカス時の再検証](https://swr.vercel.app/ja/docs/revalidation)
- [ネットワーク復帰時の再検証](https://swr.vercel.app/ja/docs/revalidation)
- [ポーリングによる定期的な再検証](https://swr.vercel.app/ja/docs/revalidation)
- [AのデータのフェッチしたあとにAを使ってBのデータを取得するといった依存フェッチ](https://swr.vercel.app/ja/docs/conditional-fetching)
- [ページネーションサポート](https://swr.vercel.app/ja/docs/pagination)
- [POST処理等における楽観的なデータ更新と失敗時のロールバック](https://swr.vercel.app/ja/docs/mutation)
- [プリフェッチ機能](https://swr.vercel.app/ja/docs/prefetching)
- [同一キーによるリクエストの重複排除](https://swr.vercel.app/ja/docs/advanced/performance)

`useSWR()`関数一つの中にこれだけ（これ以上も！）の機能が詰め込まれており、まさによくばりセットと言わんばかりなのがわかるかと思います。
中でも特に[依存フェッチ](https://swr.vercel.app/ja/docs/conditional-fetching)は宣言的UIの仕組みをうまく使った非常に逸品な機能です。
また`fetcher`の内容は実装者に委ねられているため、REST APIなのかGraphQLなのかといった通信プロトコルやそれに伴う通信ライブラリに依存せず任意に選択できるのも大きな特徴です。

React SWRについて語るだけで何本か記事がかけてしまいそうなので、詳しく知りたい方は[公式ドキュメント](https://swr.vercel.app/ja/docs)か下記の先人様の記事に任せたいと思います。

- [SWRを使おうぜという話2022 - Zenn](https://zenn.dev/mast1ff/articles/5b48a87242f9f0)
- [そうです。わたしがReactをシンプルにするSWRです。 - Zenn](https://zenn.dev/uttk/articles/b3bcbedbc1fd00)

:::note info
個人的な感想ですが、「情報更新の余地のある時はとにかくデータフェッチを行って最新にしていくぜ！仮に同じデータを何度も取得することになったとしても古い情報を画面に出し続けてるよりはいいよね！」という勢いを感じます（とはいえ各イベントごとの取得頻度はオプションで調整可能です。）
:::

## SWRの機能をComposeで再現してみる

さて、ここまでが前置きです。
これまでComposeにおけるAAC ViewModelの立ち位置、React Hooksの台頭とそこからComposeが受けた影響、宣言的データフェッチライブラリであるSWRの出現、などを語ってきました。
そこでSWRのようなフック機構をComposeに持ち込みつつ、そのまま状態ホルダー（AAC ViewModel）の代わりとして活用することを目指してみます。

ComposeにおいてSWRのインターフェースはベストとは限らないかもしれませんが、そこを吟味していくとキリがないので今回は本家SWRをそのまま再現してみました。

### SWR for Compose

その上で移植してみたものが下記になります。
流石にこの場で実装コードまで紹介していくとキリがないのでライブラリとして[MavenCentralで公開](https://search.maven.org/artifact/com.kazakago.swr.compose/swr-android)しています。

現状Androidでのみ動作します（Compose Multiplatformはサポートしていません）

https://github.com/KazaKago/swr-compose

### 実際に利用してみる

結論から言うと`AAC ViewModel`を一切使わずとも十分実用的なアプリケーションをより少ないコードで構築できました。
本家SWRと同様に高いUXをJetpack Compose上で提供できていると思います。

下記のGIFはリポジトリ内の[exampleモジュール](https://github.com/KazaKago/swr-compose/tree/main/example)で実装した簡単なTODOリストです。
アプリ内にデータの取得や更新に1秒かかるデータストアを持っており、それをサーバーの代わりとして仮想的に通信処理を再現しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129872/9fd531a9-6774-d695-9072-b8a3ab4381d0.gif" width=320> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129872/1ca5a9bf-cad6-8037-9ace-6dadd2c56c70.gif" width=320>
正直このGIFだけだと便利さが伝わりにくいかもしれないですが、データ取得をSWRで記述するだけで`stale-while-revalidate`の戦略に沿ったUXを得ることができます。
一度読み込めばキャッシュされて次回から即時反映されることはもちろんのこと、フォーカス時(=`onResume()`時)、ネットワーク復帰時に自動的にフェッチが走って最新に更新され、他の場所で同一キーでの更新があれば自動的にre-composeされてすべての利用箇所で反映されます。
またエラー発生時は[指数バックオフアルゴリズム](https://aws.typepad.com/sajp/2015/03/backoff.html)に沿って自動的に再フェッチが走ります。

## SWR for Composeの使い方

### 基本的な使い方

build.gradleのdependenciesに下記を追加してください。   
[![Maven Central](https://img.shields.io/maven-central/v/com.kazakago.swr.compose/swr-android.svg)](https://central.sonatype.dev/namespace/com.kazakago.swr.compose)[![javadoc](https://javadoc.io/badge2/com.kazakago.swr.compose/swr-android/javadoc.svg)](https://javadoc.io/doc/com.kazakago.swr.compose/swr-android)[![Test](https://github.com/kazakago/swr-compose/workflows/Test/badge.svg)](https://github.com/kazakago/swr-compose/actions?query=workflow%3ATest)[![License](https://img.shields.io/github/license/kazakago/swr-compose.svg)](LICENSE)

```kotlin:build.gradle
implementation("com.kazakago.swr.compose:swr-android:[LATEST_VERSION]")
```

使い方は本家と概ね同じです。

```kotlin:Compose
private val fetcher: suspend (key: String) -> String = {
    getNameApi.execute(key)
}

@Composable
fun Profile() {
    val (data, error) = useSWR("/api/user", fetcher)

    if (error != null) Text("failed to load")
    else if (data == null) Text("loading...")
    else Text("hello $data!")
}
```

`useSWR()`が複数の値を返すのに[分解宣言](https://kotlinlang.org/docs/destructuring-declarations.html)を用いています。
ゆえに展開する際、言語仕様の違いから本家SWRと違いプロパティの順番に依存していることに注意してください。
もちろん冗長に感じる場合は分解せず利用することも可能です。

```kotlin:Compose
val state = useSWR("/api/user", fetcher)
Text("hello ${state.data}!")
```

### サポートされている機能・オプション

本家を参考に実装しています。
React特有の仕組みを使った機能についてはサポートしていません。

| 機能名                                                                  | ステータス                          | Note                              |
|-------------------------------------------------------------------------------|---------------------------------|--------------------------------|
| [Global Configuration](https://swr.vercel.app/docs/global-configuration)      | ✅                               |                                |
| [Data Fetching](https://swr.vercel.app/docs/data-fetching)                    | ✅                               |                                |
| [Error Handling](https://swr.vercel.app/docs/error-handling)                  | ✅                               |                                |
| [Auto Revalidation](https://swr.vercel.app/docs/revalidation)                 | ✅                               |                                |
| [Conditional Data Fetching](https://swr.vercel.app/docs/conditional-fetching) | ✅                               |                                |
| [Arguments](https://swr.vercel.app/docs/arguments)                            | ✅                               |                                |
| [Mutation](https://swr.vercel.app/docs/mutation)                              | ✅                               |                                |
| [Pagination](https://swr.vercel.app/docs/pagination)                          | ✅                               |                                |
| [Prefetching Data](https://swr.vercel.app/docs/prefetching)                   | ✅                               | Available by `useSWRPreload()` |
| [Suspense](https://swr.vercel.app/docs/suspense)                              | ❌                               |                                |
| [Middleware](https://swr.vercel.app/docs/middleware)                          | ❌                               |                                |

[React SWRのオプション](https://swr.vercel.app/ja/docs/options)についても代表的なものはカバーしています。

| オプション名                                               | ステータス | デフォルト値                                                                      |
|-----------------------------------------------------------|--------|------------------------------------------------------------------------------------|
| suspense                                                  | ❌      |                                                                                    |
| fetcher(args)                                             | ✅      |                                                                                    |
| revalidateIfStale                                         | ✅      | true                                                                               |
| revalidateOnMount                                         | ✅      | true if `fallbackData` is not set                                                  |
| revalidateOnFocus                                         | ✅      | true                                                                               |
| revalidateOnReconnect                                     | ✅      | true                                                                               |
| refreshInterval                                           | ✅      | 0.seconds (disable)                                                                |
| refreshWhenHidden                                         | ✅      | false                                                                              |
| refreshWhenOffline                                        | ✅      | false                                                                              |
| shouldRetryOnError                                        | ✅      | true                                                                               |
| dedupingInterval                                          | ✅      | 2.seconds                                                                          |
| focusThrottleInterval                                     | ✅      | 5.seconds                                                                          |
| loadingTimeout                                            | ✅      | 3.seconds                                                                          |
| errorRetryInterval                                        | ✅      | 5.seconds                                                                          |
| errorRetryCount                                           | ✅      |                                                                                    |
| fallback                                                  | ✅      |                                                                                    |
| fallbackData                                              | ✅      |                                                                                    |
| keepPreviousData                                          | ✅      | false                                                                              |
| onLoadingSlow(key, config)                                | ✅      |                                                                                    |
| onSuccess(data, key, config)                              | ✅      |                                                                                    |
| onError(err, key, config)                                 | ✅      |                                                                                    |
| onErrorRetry(err, key, config, revalidate, revalidateOps) | ✅      | [Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) algorithm |
| compare(a, b)                                             | ❌      |                                                                                    |
| isPaused()                                                | ✅      | false                                                                              |
| use                                                       | ❌      |                                                                                    |

細かい使い方に関しては本家ドキュメントを読んでもらえれば本ライブラリもある程度同じように使えるはずです。
また、[exampleモジュール](https://github.com/KazaKago/swr-compose/tree/main/example)では一通りの機能を使って実装しているのでそちらもご参照下さい。

## Androidの推奨アーキテクチャとのギャップについて

ここまでReact Hooksに習った宣言的データフェッチを見てきました。
上記の[SWR for Compose](https://github.com/KazaKago/swr-compose)は宣言的データフェッチの実装の一例に過ぎませんが、様々な要件を満たす機能を一つの関数に詰め込むことができるため、ボイラープレートコードをできる限り少なくしてアプリケーションを薄く保つことが比較的容易になることが少しは伝わったのではないでしょうか。
SWRのような仕組みを用意して使うとケース次第ではAAC ViewModelどころかRepositoryパターンすら不要と言えるでしょう。

しかしながら、Android Developersの[アプリ アーキテクチャ ガイド](https://developer.android.com/jetpack/guide)や[Android アーキテクチャに関する推奨事項](https://developer.android.com/topic/architecture/recommendations)を見てみるとアプリが大規模になればなるほど、レイヤーを分けて責務の分離を行いましょうと案内されています。
今回紹介したSWRによる宣言的データフェッチはアプリケーションを薄く保ちやすい分、UIとデータ取得のロジックが近くなりがちで**Android Developersで推奨されているレイヤードアーキテクチャとは逆行する側面もある**ことに留意してください。
（別にSWRとレイヤードアーキテクチャは相反する概念というわけではなく組み合わせられなくはないですが、よくばりセットとしてのSWRの魅力は薄まります）

当然ながらUIとロジックが近いと責務分離は難しくなるので、複雑なデータ処理やオンデバイスの処理に関しては独自フックやAAC ViewModel、ドメインレイヤーなどを活用してうまく責務の分離を行っていくことが大事です。

:::note info
個人的な主観ですがSWRなどのHooksを軸にした開発は現在Androidアプリ開発で普及している`AAC ViewModel`+`Repository`と比べると**Easy**（≒書く量が少ない）ですが**Simpleではない**（≒記述場所に迷いやすい）印象です。

とはいえ昨今のシステムでは複雑なロジックはサーバーに委譲されるのが一般的で、またGraphQLなどの技術の発展もあってフロントエンド自体に求められる責務が以前よりシンプルになってきているはずです。
レイヤーを細かく分けてより責務分離を徹底していくほうが適したアプリケーションもある一方、SWRのような汎用的な仕組みで十分カバーできるアプリケーションも多いのではないでしょうか。
:::

## 伝えたいこと

宣言的UIを始めとする宣言的のトレンドはモバイルアプリを含むフロントエンド全体で散見されます。
根底にある思想はいずれも近いものの、各コミュニティにおける流行りや注目されているポイントは様々です。
歴史の深いコミュニティで普及している事柄から学ぶことは多いと感じています。

今回私はReact SWRの使い勝手をそのままComposeで再現することを目指しましたが、本当に大事なのは丸パクリすることではなくその文脈や概念を理解して応用できるようになることです。
SWRは宣言的UIの特徴をデータ取得に適用したものでしたが、ほかにも様々な機能で応用できる部分は数多くあると思います。Composeは完全な関数コンポーネントということもあり、Hooksのような仕組みと非常に相性が良いです。

つまり何が言いたいかというと、これから宣言的であることをフルに活用した便利な仕組みやライブラリが数多く生まれてくることを期待しています！（他力本願）

## 参考

- [Android Developers](https://developer.android.com/)
- [SWR](https://swr.vercel.app/)
- [TanStack Query](https://tanstack.com/query/)
- [SWRを使おうぜという話2022 - Zenn](https://zenn.dev/mast1ff/articles/5b48a87242f9f0)
- [そうです。わたしがReactをシンプルにするSWRです。 - Zenn](https://zenn.dev/uttk/articles/b3bcbedbc1fd00)

[^1]: [I/O 2019のCompose発表時の公演](https://www.youtube.com/watch?v=VsStyq4Lzxo&t=872s)で　`It's inspired by frameworks like React, Litho, Vue.js, and Flutter,` と明言されています。
[^2]: https://github.com/vercel/swr/releases/tag/2.0.0-beta.1 より