taiki-t's diary

React Native, Rails そして雑多な記録: The world is waiting for you to give it a meaning.

Elmについて少し調べた

generic な connection fragment をRelay Modernで書きたいメモ

複数の親要素が、共通の子要素をもち、その子要素がconnectionな時にfragmentを共通化する方法が知りたい。 よくわからないのでメモ。

この方法でやろうとしたけどエラー出るよね:
Spread fragment through Connection · Issue #1983 · facebook/relay · GitHub

classicの時からのトピック。誰もわかっていないのである:
Fragment templates · Issue #170 · facebook/relay · GitHub

最初のやつがspreadされるようになればいいんだけどな?

進展があったらまた更新

Implement a simple caching for Relay Modern.

update - 2017/11/07: Consulting official docs may make you even happier: Add Caching docs by felippepuhle · Pull Request #2121 · facebook/relay · GitHub. It's not merged by now.


Unlike Relay Classic, Relay Modern doesn't come with an automagical caching system out of the box. Instead, it has an utility class to help you implement a cache system.

In this article I'll describe an example implementation of a simple caching. I don't explain much of details but you'd understand if you're already familiar with Relay Modern and I believe you're so as trying to implement a cache.

Code

const {
  Environment,
  Network,
  RecordSource,
  Store,
  QueryResponseCache,
} = require('relay-runtime');

const yourEndPoint = 'https://example.com'

const headers = {
  'content-type' : 'application/json',
}

const cache = new QueryResponseCache({size: 100, ttl: 100000});

function fetchQuery(
  operation,
  variables,
  cacheConfig,
  uploadables,
) {

  const queryId = operation.name

  const cachedData = cache.get(queryId, variables);

  // Handle force option in RefetchOptions
  // See: https://facebook.github.io/relay/docs/pagination-container.html
  // https://facebook.github.io/relay/docs/refetch-container.html
  const forceLoad = cacheConfig && cacheConfig.force

  if (!forceLoad && cachedData) {
    return cachedData;
  }

  if (forceLoad) {
    // clear() means to reset all the cache, not only the entry addressed by specific queryId.
    // See blog comments for more details.
    cache.clear()
  }

  return fetch(`${yourEndPoint}/graphql`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      query: operation.text, // GraphQL text from input
      variables,
    }),
  }).then(response => {
    const data = response.json();
    // A cache key, queryId in this code, should be unique per query.
    cache.set(queryId, variables, data);
    return data
  });
}

// Create a network layer with the fetch function
export const network = Network.create(fetchQuery);

Related tweets.

Relay Modernに移行した

今作ってるアプリ、Lylica - 街のおすすめが分かるSNSをRelay ClassicからRelay Modernに移行した。

動機

今後機能を追加していくにあたり、今のうちにえいやとやっておきたかったからだ(とある機能の追加に腰が重かったから先にこっちに着手したというのは内緒)。

以前 Relay Modernへの移行検討をした - taiki-t's diary という記事を書いてその時にはいわゆるlocal stateがサポートされるまでModernに移行するつもりはないと言っていたけど、結局まだサポートされていない*1まま移行した。まあえいやって思っちゃったから。

パフォーマンス

体感的にまあアプリのパフォーマンス的にはそこまで変わりはない。計測はしてない。OptimisticResponseはClassicの頃から最強。サーバから返ってくるであろうデータをあらかじめ定義することで、サーバからデータが返ってきたものとしてビューを更新してくれるので爆速。サーバー側のエラーなどでデータ更新ができなかった場合は自動でロールバックしてくれる。更新できたら見た目にはそのまま。さいつよさしかない。バックエンドherokuで動かしてレイテンシ800msとかあっても感じさせない、というか原理的に見ためには存在しない。やばい。

気分

気は楽になった。Classicのスタイルでコンポーネントやmutationを追加するときに「うっ」って思ってたのが消えた。 そして、来たるSubscriptionsに対応できるぞーという楽しみができた。よかった。最高。

かかった手間

これ:

上記はほぼmutations以外のRelayを使ったコンポーネントを置き換えた時間。スパイク的に1箇所ぐらいmutationも書き換えたと思う。 で、上記以外のmutationsの書き換えにかかった時間は、9時間53分*2。1日かけて15個ほど書き換えた*3

手順

親切なことに移行の手順書みたいなのが用意されているのでそれをベースにやった。

Conversion Playbook - Relay Docs

上記の手順通りにうまくいったのは Step 0: Install Relay v1.0 までだった

Step 1: Incrementally convert to Relay Compatで、用意されていた移行スクリプト(まじ親切)を実行し、 シンプルなコンポーネントは自動でcompatモードに移行できた。

しかしその後、「手動でやらなきゃいけないよ」と上述のスクリプトが親切にコメントインしてくれた箇所は、compatモードで動かないことが試行錯誤ののち判明した。 ので、該当箇所は1箇所ずつ手動でいきなり Modernにえいやしていくことにした。 結果として手順は

  1. Relay Modernのインストール
  2. スクリプトで一部コンポーネントをcompatモードに移行
  3. ファイル名をCamelCaseに変更
  4. 自動でできなかったところをcompatを経由せず一つ一ついきなりModernに移行
    1. 実験的に途中1箇所mutationを置き換え
  5. データ取得については全てModernに移行(Relayの読み込み元をreact-relay/compatからreact-relayに移行)
  6. mutationsを一つ一つ手で書き換え
    1. 途中cacheを実装。Relay Modernではデータのcacheは自動でやらなくなったため。cacheの実装については別途記事にしようかと思う。
  7. 完全にRelay Modernに移行 (react-relay/compat, react-relay/classicからの読み込みを全てreact-relayに。)

ハマったとこ

コンポーネント、ファイル名

手順にもちょろっと書いたけど これ:

ので、ファイル名とimportしてるところをガッと書き換えた。そこまでやって、一部機能がcomapモードに対応してないことが試行錯誤の結果判明したので結局いきなりModernにいくことに。

Rootのクエリ名

上記についてはまあ最初からファイル名もCamelCaseで書いてある人には関係のない話だったろう。Facebook内部ではそういう規約でやってるんだと思う。他、ルートとなるクエリの名前にもFacebook内部の仕様が漏れ出ててる雰囲気があって、Queryっていう名前じゃないとcompatモードを通せなかった。RootQueryってしてたからそこもはまった。この制約はcompatモードのみで、Modernに完全に移行してしまえば関係ない。関係あったらアプリの後方互換性的にしんどかったなあ。この問題は今後のアップデートで解消される予定

キャッシュ

キャッシュをするなら手で有効にする必要がある。キャッシュ機構自体はあるので、それをちょろちょろっと組み合わせればできる。でも知らなかったしドキュメントもなさげ。

追記 2017-09-05: キャッシュの実装についてサンプルを書いた: Implement a simple caching for Relay Modern. - taiki-t's diary

Connections

例えばモデルとしてポストがあって、ポストがたくさんコメントを持つというとき、コメントを取り扱うのがconnections。has manyな関係を取り扱う感じ。書き方がだいぶ変わってて、あとcompatモードじゃ動かなくて、ドキュメントも薄かった。まあやった。自動で変換してくれないところの一つ。てか自動で変換してくれないところはそれぞれやはりつらみがある、 Relay 1.2.0が都合よく移行中に出てきたので、それで助かった部分もある。Connectionsの再取得の書き方など、1.2.0より前は大変そうだった。

mutation成功、失敗時の処理

ClassicではonFailure, onSuccess的な二つのコールバックでサーバーからのエラー、成功時処理をそれぞれ書いてた。Modernになってからは見た目にはonErroronCompletedというコールバックになってて、そのままClassicの感じかなと思ってたら挙動が異なっていた。サーバーからのレスポンスは失敗でも成功でもonCompletedで処理するようになっててonCompletedでサーバーから返されたエラーのハンドリングができるとはwebのドキュメントにはなかった(レポジトリのやつは更新されてた)。onErrorRelayがエラーに直面した時に呼ばれる。

さいごに

全体的な感想としては、やってよかったなと。書くようになったところと書かなくてよくなったところのバランスが、Classicと比べてよくなってると思う。特にmutations周りはよくできてるなーと言いたくなった。ただ、書くようになったところのドキュメントがまだ薄いので、ね。ドキュメントもっと充実するといい。

まだ色々あったようなきがするけど疲れたのでここら辺で。気が向いたら or リクエストがあれば追記しようと思う。ところでこの記事誰かの役に立つのだろうか?笑

リンク

Relay - A JavaScript framework for building data-driven React applications

Don't break in compat mode when root field is not named Query by robrichard · Pull Request #1962 · facebook/relay · GitHub

GraphQL error handling in Relay Modern · Issue #1913 · facebook/relay · GitHub

[Compat] Filename is expected to use the same case of container variable · Issue #1893 · facebook/relay · GitHub

[Modern] Paginating a connection in the root query · Issue #1705 · facebook/relay · GitHub

Lylica - 「街のおすすめ」が分かる位置情報SNS

Lylica - 街のおすすめが分かるSNSを App Store で


*1:機能はあるかもしれないけどドキュメントがなく誰も使い方がわからない [Modern] Client schema docs and examples · Issue #1656 · facebook/relay · GitHub

*2:

  1. terminal: 6h24m
  2. simulator: 1h37m
  3. localhost:8081: 40m
  4. github.com: 36m
  5. 他ツール: 12m

計測には RescueTime 使ってる。いい。

*3:今は自分しか開発陣はいないので、やりたい時にやるスタイル。逆に4時間とかしか稼働しない時もある

メモ: ファイル名をキャメルケースにするRubyワンライナー

というわけでgraphqlを含むファイルの名前をCamelCaseにしたかった

git grep -l graphql | ruby -nle 'File.rename($_, $_.split("-").map{|w| w[0] = w[0].upcase; w}.join)'

参考

rubyでsnake_caseをCamelCaseへ変換する - Qiita

正規表現やワイルドカードでファイル名を一括変換する - システム開発メモ