Posts for: #Vue.js

フロントエンドの技術選定

24時に BOOK AND BED TOKYO にチェックインして雑多なことして25時過ぎには寝て8時過ぎに起きてチェックアウトした。それから新幹線に乗って神戸まで戻ってきた。東京・品川から新神戸間は、往路は EX早特21ワイド だと12,630円で、復路は自由席で14,420円だった。私の中で時間の制約はストレスやエネルギーを使う。帰りは時間に縛られたくないという思いで新幹線の駅に着いてから自由席を買うようにしている。一方で2千円近い差額も大きいので次回以降は帰りの新幹線もEX早特21ワイドで取ることにした。

フロントエンドの調査

昼過ぎに家に戻ってきて洗濯や片付けしたら疲れてまた寝てた。晩ご飯食べて21時ぐらいからオフィスで作業してた。猫みたいな生活。オフィスからお手伝い先のネットワーク接続の設定をやったりしながらフロントエンドのコードを読んでみた。これは作り直した方がよいだろうと私の中で決意して、どういった技術で作り直すかの技術選定のための調査を開始した。既存のフロントエンド開発の背景や経緯を知らないのでまだ確定ではない。提案の準備のために調査をしておく。

ここ最近 svelte の人気があるのをみかける。1年ほど前に三ノ宮.devで教えてもらってチュートリアルをやってみて、そのときは特にどうとも思わなくて、こんなやり方もあるんやな程度にみていた。その後 vue.js (nuxtjs) での開発を半年間ほど経験して、思いの外、私にとって vue.js がよいものにはみえなかった。react よりも簡単と聞いていたけど、私にとってはあまりそうは思えなかった。vue.js は vue.js なりの難しさ (学習コスト) があるように感じられた。管理画面のような小規模な用途に react や vue.js のようなリッチなライブラリ・フレームワークを使わなくてよい方法があるかを考えたときに svelte を思い出した。svelte の実際のアプリケーションのサンプルコードとして次のコードを読んでいた。

vue.js の single-file components は svelte の前身である ractive.js のコンポーネント の概念に影響を受けているという。従って、svelte のコンポーネント開発は vue.js と考え方が近いものの、dom 操作は svelte のコンパイル時にコード生成するので仮想 dom は使わない。これがパフォーマンス上の大きなメリットと言われている。react や vue.js よりもずっと軽量なコンパイラ・フレームワークと言える。次のページに複数のフロントエンドの技術の流行をまとめている。svelte はこの2年ぐらいで人気が急上昇していることがわかる。

また react と vue.js の現状もちゃんと把握しようと調べていて次の記事がおもしろかった。

vue.js は vue3 で react になろうとしていて、その過程の過渡期には様々な問題を抱えているように私からはみえた。

  • vue2 と vue3 は互換性がない
  • vue3 移行へのエコシステムの本気度がみえない
  • vue2 の開発者が本当に vue3 を求めているのか懐疑的
  • シェアだけみたら vue.js よりも react の方が高い

フォームの enter key の振る舞いと制御

1時に寝て3時に起きて5時までだらだらしてて8時に起きた。季節の変化のせいかな?夜眠れない生活が普通になってきた。最近セブンイレブンのマスカット紅茶をよく飲んでいるのでカフェインの摂り過ぎなのかもしれない。

vuetify の v-form の enter key 無効化

あるフォーム画面でテキスト入力欄で enter key を押下すると xhr リクエストが送信されてしまう。これがフォームのデフォルトの振る舞いかどうか、私はフロントエンドに詳しくないからよくわからない。検索などはその方が便利なときもあるだろうからそういう振る舞いがあることは知っている。業務の重要な情報を誤って確定してしまってはいけないから、画面によっては禁止した方がよい状況もある。vuetify の v-form を使っている画面だとデフォルトで enter key を入力すると submit 処理が実行されてしまう。パラメーターに渡される event 情報からもマウスクリックとキー入力の見分けがつかない。

それぞれのコンポーネントの events をみると、v-form は inputsubmit しか対応していない。v-form の設定で直接 enter key 入力のイベント制御はできない。Binding Native Events to Components によると、そういった状況のために .native を使うと直接イベントをフックできるらしい。ここで v-text-field は keyup ではなく keydown のみを提供しているせいか keydown を次のように prevent してあげることでテキスト入力欄で enter key を押下しても submit 処理は呼ばれなくなった。但し、副作用として v-form の slots にあるすべてのコンポーネントの enter key の keydown イベントを prevent してしまう。

  <v-form
    @submit="submit"
    @keydown.native.enter.prevent
  >

vuetify の issue をみていると過去には無効だったものを有効化したようにもみえる。なにが正しい振る舞いなのかよくわからないし、どうやって制御するのが正しい方法なのかよくわからなかった。

v-data-table のカラムのソートがよくわからない

23時に寝て3時に起きて軽く apple イベントをみて寝て6時に起きた。

画面周りのリファクタリング

週明けに私が作った画面が本番環境にリリースされて運用を経てフィードバックが返ってきた。主には使い勝手の改善や要望だけど、何にしても実際に使ってもらってフォードバックがくるのは楽しい。丸1日リファクタリングしていて要望があったものはすべて改善できた。インフラ・バッチ処理、サーバーサイド、フロントエンドのすべてを担当しているから私が関わっているところなら適材適所にリファクタリングできる。システム全体を通してやりたいことを独力でできると楽しい。これは人間の独占欲や支配欲を刺激する。おそらくマズローの欲求でも高次の欲求に属するのだと思う。

v-data-table の props headers でカラムの値に対してソートができる。ソート可能に設定すればあとは自動的にやってくれるのかと思いきや、自分で key function を実装しないとソートはされるけど正しい並び順にはならない。key functoin の返り値が number なので -1, 0, 1 の値でソートの入れ替えを実現しているようにみえる。javascript は true => 1, false => 0 と評価されるので単純な比較演算の結果からは意図したソートにならないからではないかと推測する。このやり方が正しい実装かはわからないけど、次のような key function を定義してあげることでソートを実行したときに意図した並び順になることを確認した。すべてのカラムにこんな実装書くの?というところに懸念はある。

{
  value: 'date',
  sortable: true,
  sort: (x: Date, y: Date) => {
    return x < y ? -1 : 1;
  },
},

レンダリングの致命的なバグ

0時に寝て6時に起きた。

vuejs のライフサイクル

先日 vuejs で 画面作り に挑戦して出来たと喜んでいたが、検索して一覧画面のデータを更新した際に、フォームも再レンダリングされないといけないところがそうなっておらず、データは置き換わっているが画面に表示される値は変わっていないという致命的なバグがあることに気付いた。普通に開発していたら気付きそうなものだが、ローカルの dev server で動かしているとコードを更新すると再レンダリングが実行されるので検索後に画面の一覧が更新されないということを見逃したんだと思う。Lifecycle Diagram もみながら適切なフックポイントの振る舞いを確認したりしていた。setup 後、初期化されてその後に mounted が動いて、その後パラメーターが更新されたときに watch して再更新をかけるといった次のコードでも意図した振る舞いになることは確認した。

  setup(props, context) {
    const data: { [key: string]: any } = {};
    return { _data: ref(data), loading: false };
  },
  mounted() {
    this._data = JSON.parse(this.item.data);
  },
  watch: {
    item(value: any) {
      this._data = JSON.parse(value.data);
    },
  },

レビューしてもらったら、それよりもパラメーターをリアクティブにした方がよいのではないかと教えてもらって次のようにした。本当は setter は不要なんだけど、なぜか初期化のタイミングで setter が呼ばれるので設けた。私の作ったコンポーネントの設計が悪いせいかもしれない。

  setup(props, context) {
    const _item = toRef(props, 'item');
    return { _item };
  },
  computed: {
   _data: {
     get(): { [key: string]: any } {
       return JSON.parse(this._item.data);
     },
     set(value: any) {
       this.$emit('update:_data', value);
     },
    },
  },

これは vue2 の Options API と呼ばれる記法で、vue3 だと Composition API を使って次のような書き方ができるというのも教えてもらった。getter だけなら Composition API でもよさそうだけど、setter もあるとこのコードはまったく簡潔じゃないなと思って Options API を使うことにした。

  setup(props, context) {
    const _item = toRef(props, 'item');
    const _data = computed({
      get: () => JSON.parse(_item.value.data),
      set: (value: any) => this.$emit('update:_data', value),
    });
    return { _item, _data };
  },

コワーキングのオンラインイベントに参加した

0時に寝て6時に起きた。

バッチ処理一覧と手動実行

2週間前からフロントエンドの画面作りを始めた。2つ作らないといけない画面があってそのうちの1つを作るのに1週間ちょっとかかった。最初に作る画面で次の順番で作業を進めた。

  1. web api のエンドポイントの整備
  2. ページング処理
  3. 検索フォームのコンポーネント作り
  4. v-data-table の slot 埋め込み
  5. モーダルダイアログと更新処理

やりたいところに関係する vuejs, next, vuetify の機能を調べたり、イベントの伝搬の仕組みを調べたりしながら作成した。一度理解したら簡単なので2つ目の画面は半日で完成して pr を出して、もうそのまま本番環境にデプロイした。1つ目の画面の方が要件が複雑で2つ目の方が簡単だったというのもあるけど、どちらもストーリーポイント5が割り当てられているチケットの作業工数は1週間強と4時間といったものになった。なんというか、ストーリーポイントは中長期でみれば、このような人間が成長して一定期間内に消化できるポイントが増えることを計測する狙いもあるけれど、短期でみたらまったくプロジェクトマネジメントには役に立たない。

最近フロントエンド開発者がチームに参加して、コードを読んだらだいぶひどいみたいなことを言ってた。開発リーダーもフロントエンドは基本的に動いたら OK とか答えてた。だから品質が悪い。

コワーキングのオンラインイベント

先日 カフーツさんのイベント のイベントに参加した。それがきっかけとなり、いとうさんが手掛けている Beyond the Coworking 〜移働の時代〜 という note のメンバーシップという有償コミュニティのようなものに入ってみた。記事を読むだけなら1,000円/月で、それ以上の付加価値サービス向けに2,000円/月という料金設定になっている。毎月 zoom でオンラインミーティングを行うというので参加してみた。いとうさんは少し前にコロナに感染して療養していたそうなので日程が急に決まったせいか、たまたま私しか参加者がいなかったので1on1みたいな感じで雑談した。コミュニティを運営するためのサービスとして何がいいかという話しをしたんだけど、たしかにこれとお勧めできるものがない。note も最近そういった機能を追加して sns になろうとしているように垣間見える。

  • note のコミュニティ機能とメンバーシップ
    • 試しに掲示板を使ってみているが、メンバーはほとんど書き込まない
      • いとうさんと私しか、ほとんど書き込みしていない
      • 但し、メンバーに質問していると掲示板をみてはいるという
    • 掲示板はストックのサービスだからリアルタイムに返信をもらうことをそもそも期待していない
    • リアルタイム性の高いサービスならチャットツールがよいのではないか?
      • slack, discord, ms teams など
    • 他のツールもどうか?
      • note の掲示板, notion, trello など

他にもコワーキングスペースをうまく運営するためにはコワーキングスペースマネージャーが必要だといとうさんは考えている。コミュニティマネージャーはコミュニティ形成を目的とするが、コワーキングスペースマネージャーは似て非なるものだという。あれもこれもできないといけないという話しをしてたら、基本的にスーパーマンを要求するポジションになるみたいw、とはいえ、求められる能力として3つをあげると次のような話しをされていた。

  • 利用者と話しができる
    • 利用者の居場所になるには、利用者の業界や業務をある程度は理解して話せないといけない
    • コワーキングだから協調のためにお互いの相互理解が必要になる
  • 人の紹介ができる
    • コワーキングだから協調のために利用者同士、または自身の人脈からマッチする人を紹介できないといけない
  • 仕事の斡旋ができる
    • ビジネスなので仕事を依頼したい人、仕事を受けたい人、仕事 (お金) がまわらないと継続できない

カフーツさんはうちのオフィスから一駅、自転車で10分の距離にオフィス兼コワーキングスペースがある。1人でお仕事をしていると相談相手がいないことの弊害 がある。身近に信頼できる相談相手がいることは重要だと思う。今後もビジネス寄りのコミュニティやコワーキングの在り方を学んでいこうと思う。

雰囲気だけで画面を作れた

0時に寝て6時に起きた。

slots で v-data-table のカラムを書き換える

昨日の続き。v-html で v-dta-table のカラム書き換えしてたら slots でやれと言われた続き。次のようなテンプレートのコードでカスタムカラムを配置するためのコンポーネントを作れば既存の vuejs の仕組みで保守もしやすそうに思う。

<my-wrapping-data-table>
  <template #[`item.data`]="{ item }">
    <my-custom-cell-layout :item="item" />
  </template>
</my-wrapping-data-table>

vuejs のテンプレートのこの構文はどういう評価をされるのかが理解できない。

<template #[`item.data`]="{ item }">

そしたら同僚がそれは次の構文のシンタックスシュガーだと教えてもらった。いずれにしても dsl 万歳って感じで私からは訳がわからない。雰囲気でテンプレート書いて動けばいいんだけど。

<template v-slot:`item.data`="row">
  <my-custom-cell-layout :item="row.item" />
</template>

その後、要素の更新処理のモーダルダイアログ画面も作って1週間以上に渡って開発していた画面を一通り作り終えた。vuejs のことわかってない素人でも雰囲気だけで動く画面は作れた (pr のときにほとんどレビューで指摘を受けなかったので大半は間違ってはないのだろう) 。簡単と言えば簡単ではある。ちなみに私が作ったものが初のページング可能な一覧画面になる。検索フォームもページングに連動してクエリを実行できるようにすべてフルスクラッチでコンポーネントを作った。

v-html は使わなくてもよい

0時に寝て7時に起きた。また日曜日は寝てた。

任意のカラムの書き換え

v-data-table の、あるセルが複雑なデータをもっていて、単純にその値を表示するのではなく、一定の構造化やレイアウトを調整した状態で表示したい。セル内の構造を書き換える方法を私は知らなかったので v-html という api を使って書き換えればよいのだと思った。しかし、これは間違いだった。間違いの訂正は翌日にやるとして仮に v-html を使うとしても xss の懸念があるのでスクリプトをエスケープしてあげないといけない。Sanitize v-html #6333 でも議論されていて vue3 はデフォルトでエスケープする仕組みが入るのかな?vue2 だと sanitize-html を使って次のようにラップすればいいと書いてあった。実際に動かしてみるとスクリプトを実行できたので v-html は危険だというのはわかった。

<div v-html="$sanitize(value)" />

この仕組みを作って pr でレビューしてもらっていたら、カラムの構造を書き換えたいだけなら slots を使えば普通にできると教えてもらった。また明日へ。

vuetify のコンポーネント調査

0時に寝て7時に起きた。

vuetify で検索フォームのコンポーネント作成

ページング処理ができた。次に検索リクエストのための検索条件を扱うフォームを汎用コンポーネントで作ってみることにした。コンポーネントを生成するための検索条件のオブジェクトを外部から渡して、あとはよしなに grid に構成要素を配置する。v-date-picker のところは本当はもっと凝った作りをしないといけない。ここでは型で分岐してコンポーネントを配置する概念を表しているだけ。v-col は cols は1から12までの数字を受け取る。この数値を調整して v-spacer を入れることで行の位置調整もできるのがひと工夫しているところ。イベントハンドラーは click:search とか click:searchClearのように名前を付け替えて、外部から意図したイベントのみをフックできるように考慮している。

<template>
  <v-container>
    <v-row dense>
      <v-col v-for="item in conditions" :cols="item.cols" :key="item.label">
        <v-switch
          v-if="Boolean === item.type"
          v-model="item.value"
          :label="$t(item.label)"
          :clearable="true"
        />

        <v-text-field
          v-if="String === item.type"
          v-model="item.value"
          :label="$t(item.label)"
          :clearable="true"
        />

        <v-text-field
          v-if="Number === item.type"
          v-model="item.value"
          type="number"
          :label="$t(item.label)"
          :clearable="true"
        />

        <v-date-picker
          v-if="Date === item.type"
          v-model="item.value"
          :clearable="true"
        />

        <v-spacer v-if="null === item.type">
          <!-- use an empty block for grid layout -->
        </v-spacer>
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="4">
        <v-btn
          v-text="$t('label.clearSearchCondition')"
          @click="_searchClear"
        />
      </v-col>
      <v-col cols="3">
        <v-btn color="primary" v-text="$t('label.search')" @click="_search" />
      </v-col>
    </v-row>
  </v-container>
</template>
<script lang="ts">
import { PropType, defineComponent } from '@vue/composition-api';
export interface SearchConditionItem<T = any> {
  type: PropType<T> | null;
  name: string;
  label?: string;
  col: number;
  value?: any;
  fromValue?: string | null;
  toValue?: string | null;
}

export default defineComponent({
  components: {},
  props: {
    conditions: {
      type: Array as PropType<SearchConditionItem[]>,
      default: () => [],
    },
  },
  setup(props, context) {
    return {};
  },
  methods: {
    _search(value: any) {
      this.$emit('click:search', value);
    },
    _searchClear(value: any) {
      for (const c of this.conditions) {
        c.value = null;
        c.fromValue = null;
        c.toValue = null;
      }
      this.$emit('click:searchClear', value);
    },
  },
});
</script>

呼び出し側ではこんな感じ。任意の conditions を渡し、検索ボタンをクリックしたときのイベントハンドラーを登録する。

<search-condition-form
  :conditions.sync="searchCondition"
  @click:search="search"
  v-on="$listeners"
/>

vuetify で初めてコンポーネントを作ってみた。雰囲気だけで実装している個人的な所感だけど、template, script, style を1つのファイルに同梱する考え方が私には馴染まない。1つのファイルに複数の構文が混在する認知負荷が気になるのと、1つのファイルに複数のコードを同梱しているメリットが私には感じられない。もしかしたら小さいシンプルなコンポーネントなら見通しがよいのかもしれない。しかし、業務での開発だと一定の複雑さをもつコンポーネントの方が大半だと思うので1ファイルが1画面におさまらない。どうせエディターを画面分割して複数画面でソースを読むのであれば、その画面のソースが1つのファイルでも別のファイルでも私にとってあまり大差ない。ファイル間の依存関係さえ適切に管理できればファイルは用途ごとに分割できた方が人間にとってわかりやすいのではないかとも思う。一方でフレームワーク側からみたら依存関係の解決はやや煩雑な処理になるので開発や依存管理がシンプルになってビルドが速くなるといったメリットがあったりするのかもしれない。どうなんだろう?

vuejs の template 調査

0時に寝て6時に起きた。

連日のサービスイン作業

引き続きサービスインの運用対応は大変そうでちゃんと検証していない修正を慌ててマージしようとしているからテスト環境まで壊れてて関係ない開発にも影響が出ていた。今日も別の施設のサービスインだったらしくて、ある機能がないとそのサービスインの切り替え作業ができないという話しだったそうで、当日に慌てて pr を作ってマージしてた。先週からわかっていた必要な機能を実装してなくて、週末は残業も休出もしてなくて、今日になって慌てて修正してマージしてた。昔の開発と比べてがんばっててできないのではなくて、いまの開発はがんばってないからできないという雰囲気になったなという印象。

vuejs の template と expression

あるフォームのコンポーネントを作ろうと思って interface を定義していてデフォルト値をテンプレート側に指定できるといいんじゃないかと考えた。というのは typescript の interface のメンバーは値を保持できないから。例えば、次のようなコードで :cols="item.col ?? 2" のように表現できたら嬉しいように思う。

<v-row dense v-for="item in conditions" :key="item.label">
  <v-col :cols="item.col ?? 2">
    {{ element }}
  </v-col>
</v-row>

余談だけど、?? は null 合体演算子という名前は知っていたけど、これを英語で何と呼ぶのか知らなかった。Nullish coalescing operator と言う。ググってみると vuejs の issue でもそこそこ議論されていて vue3 からサポートするとしながら、根強い要望があるのか? vue2 でも 2.7 でサポートしたらしい。こういうモダンな javascript の expression を ESNext syntax と呼んだりするみたい。それすらも知らなかった。

たまたまうちで使っているのは vue 2.6.14 なので vue 2.7 で動くのかどうか検証できないけど、いま使っている nuxtjs2 との依存関係があるのでそれ次第で vue 2.7 にアップグレードの可否が決まるらしい。全然フロントエンドの開発がわからないので、こういう基本的なところで引っかかると背景を調べるのに時間がかかる。

vuetify のイベントリスナーの調査

4時に寝て7時に起きた。日曜日にたくさん寝たせいか、昨日は眠れなかった。サービスインの運用対応はまだまだドタバタしていてデータの不整合に苦しんでいるみたい。大変そうだけど、なにもやることない。

vuetify の v-data-table のページング処理

昨日から vuetify のページング処理を調査している。コンポーネント的には2種類ある。

  • v-pagination: 汎用のページングコンポーネント
  • v-data-table: data table のコンポーネント (ページング機能がある)

既存のアプリケーションは nuxtjs で実装されているので vuetify や vue.js のサンプルコードをそのまま動かせるわけではない。丸1日、試行錯誤していてビューと値の束縛、イベントの伝搬などの振る舞いをだいたい理解できた。宣言的なフレームワークなので振る舞いを理解できれば開発量は少なく済む。但し、理解するまで振る舞いを理解するのに設定を試行錯誤で試して動かすのでデバッグは時間がかかる。一覧画面で使っている v-data-table のページング処理対応から始める。Server-side paginate and sort を参考にしながら v-data-table に加えた主な変更はこれら。

<v-data-table
  ...
  :server-items-length="serverItemsLength"
  :disable-pagination="disablePagination"
  :hide-default-footer="hideDefaultFooter"
  :options.sync="options"
  :footer-props="{
    itemsPerPageOptions: [10, 20, 50, 100],
  }"
  v-bind="$attrs"
  v-on="$listeners"
>

v-data-table をラップするコンポーネントでは次のようにイベントリスナーを登録する。

<my-nice-component
  ...
 :server-items-length="serverItemsLength"
 :disable-pagination="false"
 :hide-default-footer="false"
  @update:options="dataOptionsHandler"
>

  async dataOptionsHandler(options: DataOptions) {
    const page = options.page ?? 1;
    const limit = options.itemsPerPage;
    const offset = limit * (page - 1);
    // limit/offset を使った web api リクエスト
  }

vm.$listeners によると v-on="$listeners" のように書くと、ラップしているコンポーネントのイベントリスナーをよしなに伝搬してくれるみたい。これはこれで便利だけど、イベントリスナーの定義がどこにもでてこないのでコード検索ができなくなる。最近の宣言的なフレームワークの流行りと言えばそうだけど、知ってないとなんで動くのかわからないコードにはなる。

たまには画面作り

1時に寝て6時に起きた。

リファクタリングとインフラ移行

ここ2週間ほどリファクタリングやらインフラ変更やらをしてきて、来週からまた新しい施設がサービスインするので区切りとしてリファクタリングは一旦終わりにする。今日がその集大成となるインフラ移行も含めた本番リリースだった。インフラ移行するときはなにかしら障害が起きる前提で待機しているものの、今日もすんなりと意図した通りに移行できて、してやったりではあるものの、モノ足りなさで拍子抜けしてしまった。また昨日から社内 wiki にも minikube の使い方、k8s cronjob の設計、バッチ処理の設計と実装についてドキュメントなどを書いていた。いままですべて私が1人で担当していたものを他メンバーでも作業できるようにドキュメント化した。近いうちにいなくなるので引き継ぎのドキュメントにもなる。

nuxt で画面作り

ここ最近2種類の web api の機能を作ったのでその管理画面も2つ作る必要がある。私はフロントエンド開発の素人なので他のメンバーが作ってくれないかと声をかけてはいたけど、みんな忙しいようなので私が作ることにした。今週は nuxtjs の新規画面の開発をがんばってみようと思う。既存のソースを読む限りはそんなに複雑ではなさそう。素人が雰囲気で実装しても動くんじゃないかと思っている。ソースコードを読んでいて url 設計はめちゃくちゃだし、一覧画面にはページング機能も実装されていない。素人がソースを読んで基本的な骨子や機能が正しく実装されてないことがわかってしまうのは品質レベルとしてなにかがおかしい。圧倒的低品質と呼ぶのか、こんなことが起こってしまうのはよい開発文化がないせいなのだろうと考えている。