mmag

ハマったことメモなど

Apolloのcache updateがやや辛そうに見える

使っているのはvue-apolloだけど。

Mutationした結果をつかってquery cacheを更新する、ということができる。これによってもう一回queryしなくてよくなるなどの効果がある。Optimistic responseと組み合わせると、迅速なフィードバックを利用者に返すことができて体験が良くなる効果も見込める。よくあるコード例は

import createCommentMutation from 'create-comment.gql'
import commentsQuery from 'comments.gql'

const newComment = { ... }

this.$apollo.mutate({
  mutation: createCommentMutation,
  variables: {
    body: newComment.body,
  },
  update: (store, { data: { createComment } }) => {
    const data = store.readQuery({ query: commentsQuery })
  
    data.comments.push(newComment)

    store.writeQuery({ query: commentsQuery, data })
  },
})

なんてやつ。store.readQueryに更新したいキャッシュのクエリを与えてキャッシュの中身を取り出し、mutationの結果を使って編集して、最後にwriteQueryでキャッシュを更新。どこか画面などに反映される。

ただ現実のアプリケーションはこんなに単純なものでもないと思っていて、例えば更新したいクエリというものがvariablesを使っていたとすると、

import createCommentMutation from 'create-comment.gql'
import commentsQuery from 'comments.gql'

const newComment = { ... }
const date = '...'

this.$apollo.mutate({
  mutation: createCommentMutation,
  variables: {
    body: newComment.body,
  },
  update: (store, { data: { createComment } }) => {
    const data = store.readQuery({
      query: commentsQuery,
      variables: { date },  // <- これ
    })
  
    data.comments.push(newComment)

    store.writeQuery({ query: commentsQuery, data })
  },
})

クエリキャッシュを取得するときもvariablesが必要になる。ということは、mutationする関数などに、mutationに必要なパラメータに加えて、どのクエリキャッシュを更新すべきか特定できるだけのパラメータを渡さないといけない。もしかすると大抵の場合はmutationに必要なパラメータから導出できるのかもしれないけど、そうでないならなんかあっちこっちからデータ引き回しそう。Vuexとかでカバーできるのか、設計が良ければそうはならないのか、はて。

ElixirのConfigに関する議論が盛り上がっている

これ

elixirforum.com

課題意識としては、Mixプロジェクトにおけるconfigはアプリケーションが起動するときに読み込まれるけれど、その一方でそれをreleaseビルドするときはコンパイルするときに読み込まれるという違いがあり、混乱のもとになっている、というもの。よくあるのはSystem.get_env環境変数を読み込む場合で、releaseを動かす環境のものを使ってほしいのに、releaseをビルドした環境のものが使われてしまい、開発中は動いてたのにいざ本番へというところで全然動かない、ということがある。

これをどうやって解決しようか、という話で、提案されているのはconfigのファイルをreleaseの中にコピっちゃおうよ、というもの。ただしそれを行うには問題があって、一般的なMixプロジェクトでは

use Mix.Config

config :my_app, foo: :bar

import_config "#{Mix.env()}.exs"  # <- これ

ということをして動的に設定ファイルを読み込んでいる。これをやられると、releaseにコピーするべき設定ファイルを知るためには一度設定を評価しないといけない。しかもMixはreleaseでは使えない。そこで、mix.exsに新しい項目を増やして、使う設定ファイルを明示的に書いておきましょうよ、ということになっている。

# mix.exs
def project do
  [
    ...,
    config_paths: ["config/config.exs", "config/#{Mix.env()}.exs"]
  ]
end

な感じ。これなら設定ファイルを評価する前にどれをコピーすればいいかわかるし、動的にimport_configする必要がなくなるので、設定の評価時にMixも要らなくなる。またこの変更に伴って、:elixirアプリケーションにApplication.Configってのを入れるよ、ということも言われている。


これを書いている時点でコメントが90ほど伸びていて盛り上がっている。若干議論が横道っぽいとこ(runtimeに使われる設定とcompile timeに使われる設定が混在してるの気持ち悪くないか?とか。これは今もそうだし、今回の提案が取り込まれても同じ)に行っている感じもあるけど、大注目スレッドであることは間違いないので今後もウォッチしてく。

Atomic Designのあいつら

Atomic Designという、コンポーネント単位で設計してく手法というか考え方みたいなものがあるわけですが、様々な解釈があるようでみんな違うこと言ってる気がしている。ここ半年くらい仕事とか趣味プロダクトで試したり、あちこちブログ読んだりしてなんとなく自分なりの解釈ができた感じがするので言語化しておく。

atoms

buttonとかh1とか、HTMLのタグにスタイルを付けたもの、くらいの感覚。atomsとatomsを並べるための、レイアウトするためだけのコンポーネントなんかもここに入ると思ってる。これ以上分割できないものとよく言われるけど、分割できそうなものでもいいんじゃないのとか思っている。例えばボタンはatomsの代表例でよく挙げられるけど、ヘリクツを言えば押す部分とテキストに分けられる。あと、ここにCSSを当てるときはmarginを持たせない。与えられた領域いっぱいに広がるようにすると再利用性しやすい。

molecules

atomsをいくつか組み合わせてできたもの。これもmarginを持たせるべきでない。

organisms

atomとかmoleculesをいくつか組み合わせたもの。よくmoleculesとorganismsの区別で迷うけれど、名前にドメインの言葉が入ったら、または入れられるならそれはorganismsだと思ってる。atomatom組み合わせたらorganismsっぽくなっちゃった、というときは、それはmolecules1個から成るorganismsなんじゃないの、くらいの認識。

templates

organismsをレイアウトするだけの存在。レスポンシブな動きはなるべくここで全部吸収する。画面幅が変わったときに配置を変えるだけでカバーできないような変化は、画面幅ごとにorganismsをつくってtemplatesが出し分ける。どうしても無理がある感じになったらpagesが多少この辺りを担う。organisms以下には絶対にmediaクエリを書きたくない。

pages

画面とかURLに対応する。templatesとカブっちゃうことがあるので、templatesいらないならそれもよし。

まとめ

ただ結局、あんまりしっかりルールみたいなの決めると例外ができたときに死んじゃうので、ゆるふわでやるのがよい。