Web Frontend Challengeに参加してきました

プログラミング

サイバーエージェント開催の2daysインターン「Web Frontend Challenge」に参加してきました!
その感想や成果物を作る上で詰まった部分をつらつらと書いていきたいと思います。

Web Frontend Challengeとは?

このインターンはコンペ形式となっていて、2日間で1から動作するWebアプリケーションを制作するというものです。
今回のテーマは「アルバムのUIを作る」というもので、最低要件自体はとてもシンプルなものでしたが、後述するように工夫の余地はいくらでもあって、とても面白いテーマだったかなと思います。

なお、基本的に用意されたAPIを使う形だったので、完全にフロントに専念できるコンペという感じでした。APIラッパーを書く猛者が現れる可能性は考えていたけれども、あくまで評価軸はフロント側に設定していたそうです。

最終的にできたもの

さて、そんなテーマで僕は、Nuxt.js に Vuetify を組み合わせて下のようなギャラリーアプリを制作しました。いつAPIがクローズされるか不明ではありますが、興味ある方は一応 GitHub / Heroku で公開していますので、そちらをどうぞ。

制作したWebアプリ「My Gallery」

コンセプトとしては、「 UI 周りはできる限り Vuetify に任せ、機能開発に集中する」という形で頑張りました。Vuetify を使うと、割と実装が面倒な Dialog や選択画面などがサクッとかけていいですね。

主に気合い入れた機能としては、

  • 画像詳細画面の下部に iPhone の「写真」アプリみたく、前後の画像を表示
  • 気に入った画像をまとめられるアルバム機能

あたりです。アルバム機能では、前から使ってみたかった Vuex ストアの内容を簡単に永続化できるライブラリ vuex-persistedstate を活用してみることができて楽しかったです。あと、無限スクロールには Intersection Observer がクッソ便利でした。このインターンで初めて存在を知りましたが、Polyfill 入れれば Safari でも問題なく動くし最高です。

作る中で得られた Tips

同一ページコンポーネント内での遷移が再利用されない

画像詳細ページは/detail/:idという風にルーティングしており、前後の画像への移動をページ移動として処理していました。
しかし、そのidのみが変わった際にコンポーネントが再利用されず、表示がちらつくという不具合に悩まされました。

Vue Router はコンポーネントを再利用する設計だと認識していましたが、Nuxt.js の場合、シームレスなトランジションのために、あえて再利用しないようにしているという設計が原因にありました。

Dynamic page recreated on param change · Issue #4663 · nuxt/nuxt
Version v2.3.4 Reproduction link Steps to reproduce Go to /foo/1 Open console Click Bar 2 link (The link only changes the bar param -> see URL) Notice in the c...

上の issue を参考に、下のコードのように明示的にコンポーネントにkeyを設定してあげることで解決しました。

export default {
  key: '_id',

  components: {
     ...
}

watch 内で $refs を使うと undefined

横スクロール要素のスクロール位置を制御する必要があったので、watch プロパティ内で $refs から DOM を取得したいといった状況になりました。
しかし、immediate: true に設定した際の初回呼び出し時はまだ $refs が用意されていない模様で、mounted() に処理を移すことで解決しました。

vuejs core · Discussions
Explore the GitHub Discussions forum for vuejs core. Discuss code, ask questions & collaborate with the developer community.

beforeRouteUpdate() でセットしたデータが消える

前述した画像詳細ページでは、何を思ったか忘れましたが、beforeRouteUpdate() でページで使うデータをセットしていました。
ところが、ここでセットしても data() に書いた初期値で上書きされてしまうという問題があります。

Data is not set with beforeRouteEnter () before created () method is called · Issue #1144 · vuejs/vue-router
Vue.js / vue-router versions 2.1.10 / 2.2.0 Steps to reproduce Fetch some data via the beforeRouteEnter () method data () { return { post: null } }, beforeRoute...

本当は、issue の最後で言われているように this.site = this.$route.async.site のような beforeRouteUpdate() で取得したデータを他で取れるようなものがあると望ましいのですが、現状解決するには、

<template>
  <div>
    <h2>Img: {{ img.name }}</h2>
  </div>
</template>

<script>
let img = {}

export default {

  data () {
    return {
      img
    }
  },

  async beforeRouteUpdate (to, from, next) {
    img = await this.loadImage(to.params.id)
    next()
  },

  ...
}
</script>

などという風に export 外の変数に保存して渡すしかないようです。なぜ watch: '$route' を使わずにこちらを使ったのかは忘れてしまいましたが、beforeRouteEnter()beforeRouteUpdate() でデータを取得する場面が出てきた方は、参考にしてみてください。

2日間の感想など

2日間の感想ですが、今回参加者が10人と比較的少人数だったことで「全員と話す機会があって仲良くできた」ことがまず言えるかなと思います。
2日目の夜に懇親会があったのですが、その前日の夜に参加者同士でご飯を一緒に行く流れになって、行ったお店がなかなか料理が出てこなかったのでたくさんお話しできて、だいぶ打ち解けることができました。

懇親会の他にも、ランチタイムは社員の方が混じって、普段どんな風に働いてるかという話からこちら側の大学生活までわいわい盛り上がりました。

また、メンターの社員さんはさすが普段業務開発をしているだけあって、相談すると30分調べて悩んでいた問題が5分で解決することがザラにあって、「SUGEEEE」と終始思っていました(笑)
CA では主に React を使っていることもあり、Vue.js をガッツリ書いている人はあまりいなかったにも関わらず、見当をつけて迅速に問題の解決策を探れるのはまさしくプロです。

最後の成果発表では参加者全員が最低要件を満たしつつ、それぞれ異なるベクトルに付加価値をつけていてとても勉強になりました。
ギャラリーアプリとしてのコンセプトをプロダクトレベルまで詰めた人、画像一覧の魅せ方にこだわった人、はたまた VoiceOver などにしっかり対応してキーボード操作も可能にした人…一人ひとり自分が思いつかなかった視点を持っていて、まだまだ知らないこと・気づけなかったことがたくさんあるなと思いました。

そして、最後の成果発表では有難いことに最優秀賞を頂くことができ、これからも頑張っていこうと強く思えた濃厚な2日間でした。

コメント