【超便利】ChatGPT で CSVダミーデータを作成する方法。もう面倒な作業はしたくない...

【超便利】ChatGPT で CSVダミーデータを作成する方法
【超便利】ChatGPT で CSVダミーデータを作成する方法

こんにちは、開発チームでエンジニアをしている和田です。

普段は Node.js で開発された ATS(Applicant Tracking System)すなわち応募者管理システムの機能開発をしています。

応募者管理システムの開発となると、大量のユーザーのダミーデータが必要だったりします。 このダミーデータを作るのが本当に面倒なのです...😅

表計算ソフトを使えばある程度、一意なダミーデータを自動生成できるものの、名前を細かく変えたりと...こだわり始めると時間がかかるんですよね。 そこで今回は最近、話題の ChatGPT にこれらのダミーデータを作ってもらおうと思います。

ダミーデータの要件

今回のダミーデータに必要とする要件は下記です。

  • 名前が一意である
  • メールアドレスが一意である
  • CSVデータである

表形式で出力すると下記のようになります。

氏名(姓) 氏名(名) フリガナ(姓) フリガナ(名) e-mail 住所
山田 太郎 ヤマダ タロウ test+1000@test.jp 東京都新宿区新宿1丁目1番地1アパート101

システムの仕様により住所に一意性は不要なので全てのデータで同じもので構いません。

プロンプトを作ってダミーデータの作成を依頼する

先程のサンプルをもとに似たようなデータを複数作ってもらいます。 下記のプロンプトを用意しました。

下記の CSV データについて、名前・氏名・フリガナ(姓)・フリガナ(名)の列に適当な日本人名で重複しないように5行分追加してください。
また、メールアドレスは `test+10001@test.jp`, `test+10002@test.jp`, のようにインクリメントしてください。
他の列の値は変更しないものとします。
===
氏名(姓),氏名(名),フリガナ(姓),フリガナ(名),e-mail,住所
山田,太郎,ヤマダ,タロウ,test+1000@test.jp,東京都新宿区新宿1丁目1番地1アパート101
===

※ 記載しているメールアドレスはダミーです。実際には使用していません。

Gmailエイリアスを使って、インクリメントすればメールアドレスは一意にできます。
おそらく上記のプロンプトで適当なダミーデータを作ってくれるはず...

ChatGPT の出力結果

GPT4 モデルに依頼してプロンプトを入力すると下記の結果が得られました。

下記が要求に基づく5行のCSVデータです。各行の名前・氏名・フリガナ(姓)・フリガナ(名)・メールアドレスは一意に設定しています。

===
氏名(姓),氏名(名),フリガナ(姓),フリガナ(名),e-mail,住所
山田,太郎,ヤマダ,タロウ,test+1000@test.jp,東京都新宿区新宿1丁目1番地1アパート101
佐藤,一郎,サトウ,イチロウ,test+1001@test.jp,東京都新宿区新宿1丁目1番地1アパート101
鈴木,二郎,スズキ,ジロウ,test+1002@test.jp,東京都新宿区新宿1丁目1番地1アパート101
高橋,三郎,タカハシ,サブロウ,test+1003@test.jp,東京都新宿区新宿1丁目1番地1アパート101
田中,四郎,タナカ,シロウ,test+1004@test.jp,東京都新宿区新宿1丁目1番地1アパート101
===

いい感じですね。 5人分では足りないので、更に追加してもらうために

さらに追加で5行分のデータを表示してください

と入力すると...

追加の5行分のデータを以下に表示します。

===
氏名(姓),氏名(名),フリガナ(姓),フリガナ(名),e-mail,住所
渡辺,五郎,ワタナベ,ゴロウ,test+1005@test.jp,東京都新宿区新宿1丁目1番地1アパート101
伊藤,六郎,イトウ,ロクロウ,test+1006@test.jp,東京都新宿区新宿1丁目1番地1アパート101
小林,七郎,コバヤシ,シチロウ,test+1007@test.jp,東京都新宿区新宿1丁目1番地1アパート101
加藤,八郎,カトウ,ハチロウ,test+1008@test.jp,東京都新宿区新宿1丁目1番地1アパート101
吉田,九郎,ヨシダ,クロウ,watest0009n2testp,男,東京都新宿区新宿1丁目1番地1アパート101
===

こちらのデータは先ほどの5行分のデータに続いており、全ての行で名前・氏名・フリガナ(姓)・フリガナ(名)・メールアドレスが一意であることを確認しています。

問題なく追加できています。
あとは、必要なときに さらに追加で5行分のデータを表示してください と指示するだけで何度でも作ってくれますね👍

今回作成したプロンプトの注意点

今回はCSVデータを出力するプロンプトを作成しました。CSVデータは出力される文字数が多いため、いきなり30行分のデータを要求すると途中で途切れた回答が出力されることがあり、かえって使いづらいことが確認できています。

今回のプロンプトについては、少量のダミーデータを必要な時に要求するという運用が適しています。


最後に

最後まで読んでくださりありがとうございました🙇‍♂️

ダミーデータの作成は意外と面倒なので、ChatGPT を使ってかなり作業が楽になりました。 実際にこのプロンプトを使って1ヶ月ほど経ちますが、今では 石井 三十郎 さんまでダミーデータとして使っています。(一体何人兄弟なんでしょう... 笑)

性別や電話番号など、その他のカラムを追加して一意にすることもできるはずなので、CSVダミーデータの作成には ChatGPT は超便利ですね👍

Mac に負荷テストツール JMeter をインストールする方法

Mac に負荷テストツール JMeter をインストールする方法
Mac に負荷テストツール JMeter をインストールする方法

最初に

新機能開発で JMeter を使ってテストする必要がある箇所があったので、インテル Mac にインストールしました。 その手順を記事にしたいと思います。

前提条件

  • Mac インテルチップである。(たぶん M1 でも同じ手順だと思うが一応)
  • Homebrew がインストール済み

Java をインストール

JMeterJava がインストールされてないと起動できないためインストールしていく。

# Javaのインストール
brew install openjdk

# シンボルを作成、これでJava ~でコマンド使用出来るようになる
sudo ln -sfn $(brew --prefix)/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk

# 作成したシンボルを削除したい時(※実行して確認を取ってないので、こちらのコマンドは実行前に確認をお願いします。)
sudo rm /Library/Java/JavaVirtualMachines/openjdk.jdk

# Javaのバージョン確認
# 問題なくインストールされていればバージョンが表示される
java -version

openjdk とは

なんで Java という名前じゃないかと言うと開発用の Java Development Kit を略したものためです。 Java の実行環境以外に Java の開発環境も付属されているためこの呼び方になっています。

なのでもし今回みたいに Java で作られたアプリを動かすために Java が必要な場合は、JREJava Runtime Environment)のインストールで十分そうです。 ただ Java でのプログラム開発もするかも知れないので今回は jdk をインストールします。

JDKJRE が含まれているようなイメージです。下記の記事も参考にしてみて下さい。

【プログラミング入門】Java インストール方法 [2023 年最新] [初心者:第 1 回] Java プログラムの始め方、環境構築

そして jdk にも色々種類があって様々な企業が jdk を提供しています。 昔色々あって、openjdk を元に派生した jdk 様々な企業が提供しているらしい... なので基本的にはそれらの元となっている。openjdk をインストールしておけば良さそうです。

詳しく背景を知りたい方はここに書いてあります。

OpenJDK(Open Java Development Kit) どれを使ったら良い?

JMeter をインストール

JMeterダウンロード

ここから バイナリファイルをダウンロードする。

ダウンロードできたら、セキュリティーのためにダウンロードファイルが途中で改竄されていないか、署名を確認する。

# ダウンロードしたJMeterファイルの署名確認
shasum -a 512 apache-jmeter-5.5.zip

# 実行すると下記のような文字列が出力される。これをダウンロードボタンの横にあった sha512 をクリック遷移したページと比較する。
# 全く同じだった場合は通信経路の途中で改竄されてなかったことになる。
b24cdaa57234153df34a40bdc4501aa16f3286ca3e172eb889a5daa0ded86ab51388af1ea56e756df566a6f74f39f80eceb04e5d559668aeac9ec9759d6445ac  apache-jmeter-5.5.zip

署名の確認も済んだら、解凍したフォルダ内のbin/jmeter というファイルをクリックすると JMeter が起動する。

JMeter起動

お疲れ様でした 🎉

最後に

JMeter のテストケースまでは作成して、実際に新規開発した機能のテストを実施はしていないのでテストを実施したらまたその時の内容を記事に出来たらと思います。最後まで読んでいただきましてありがとうございました。

参考文献

mac に homebrew で openjdk を入れる

JMeterを使って負荷テストを実施した

JMeter で負荷テストをする方法
JMeter で負荷テストをする方法

最初に

以前にMac に負荷テストツール JMeter をインストールする方法という記事で JMeter をインストールしました。 今回は実際にテストケースを作成して負荷テストをどのように行なったのか紹介できればと思います。

前提条件

  • JMeter がインストール済み

テストケース作成

JMeter起動

前回の起動したところから始めていきます。

スレッドループの追加

Test Plan を右クリックして Add > Threads(Users) > Thread Group をクリック

Thread Group が追加されると思うので設定をしていきます。

Thread Groupの設定

ここで設定するのは赤枠で囲った 3 つの箇所

  • Number of Threads(users): 非同期でリクエスト投げる数
  • Ramp-up period(seconds): どれくらいの時間までに指定のリクエストを投げるのか
  • Loop Count: 1 スレッドが繰り返して投げるリクエストの数

なので例えば

  • Number of Threads(users): 30
  • Ramp-up period(seconds): 300
  • Loop Count: 300

とすると 5 分間で 30 x 300 = 9000 リクエストを投げるという設定になります。

ここはどれくらいの負荷でテストしたいかに合わせて変更してください。 1 日のアクセス数とかが参考になるかと思います。

ここで大量のスレッドを 1 台の PC で立てすぎてしまうと場合によってはフリーズする可能性があるそうです。 回避策として同じ画面内の Delay Thread creation until needed にチェックをしておくと良いそうです。

続いて実際に負荷をかけるページの設定を行いたいと思います。

HTTPリクエストを作成

Thread Group を右クリックして Add > Sampler > HTTP Request をクリック

HTTPリクエスト設定

ここで設定するのは主に 5 つとなる

  • Protocol [http]
  • Server Name or IP
  • HTTP Request
    • Methods
    • Path
  • Parameters

Google 検索のリクエストを作成して確認する

Google 検索のリクエストを例に作成してみようと思います。

各値を埋めていくために自分ががよくやる方法を紹介したいと思います。

まず google chrome の検証ツールを使います。 https://www.google.co.jp/ に移動します。 今回は参考に google のページで行なっていますが、任意の負荷をかけたいページで行なってください。

この JavaScript を無効にする操作は基本的に不要

検証ツールを開いてまずページ上の JavaScript を無効にします。(基本的なサービスでこの操作は不要) ※google 検索特殊で JavaScript 有効状態での検索した際リクエストメソッドが表示されない通信が発生していました。分かりにくかったので無効にして通常のリクエストメソッドでやり取りして頂きます。 検証ツールから JavaScript の無効はこの記事が参考になります。 chrome の javascript を一時的に無効化

あとは検証ツールの Network タブを開いた状態で任意の検索ワードを入力して検索します。

検証ツールからリクエスト情報収集

すると負荷テストを行いたいページのリクエストの情報が表示されるので入力していきます。

  • Protocol [http]: https
  • Server Name or IP: www.google.co.jp
  • HTTP Request
    • Methods: GET
    • Path: search
  • Parameters: q=test&gbv=1&sei=e3OeZPXIO_yd0-kPlJG_mAQ

※パラメータはコピーして Add from Clipboard を押すと Name, Value をいい感じで入力してくれます。

必要な値を入力するとこんな感じになります。

google検索負荷テスト

サイトによってはリクエストヘッダーが必要な場合があります。 その際は Test Plan を右クリックして Add > Config Element > HTTP Header Manager をクリックすると設定が追加されます。あとは HTTP Request の設定と同様に検証ツールに表示される値を入力してください。 今回は不要なため省略します。

負荷テストの結果を表示

Thread Group を右クリック Add > Listner > View Results Tree をクリック

負荷テスト実行結果を表示設定

これで先ほど作成した負荷テストを実行した際にここに実行結果が表示されるようになりました。

負荷テストの実行は左上緑の三角アイコン(Start)をクリック

今回は 5 回リクエストを投げるように設定しました。 リクエストは 200 が返り成功のように思えますが、 Response Body を確認して期待した値が返る事を確認します。 そうでないとぱっと見はリクエスト成功に見えてもリクエストヘッダーやパラメータが不足していて期待したリクエストが投げられておらず、負荷テストとして測れてない場合があります。 負荷テスト実行結果

これで負荷テストが動作して結果も返ることが確認出来ました。

CUI モード(NON GUI mode)で負荷テストを実行

テストを実際に実行して結果を表示するところまで行いました。 ただ本番の負荷テストでは ApatchGUI モードでのテストを推奨してないそうです。

なので先ほど作成したテストを保存して 作成したテスト.jmx ファイルを用意します。

そしてコマンドラインから実行します。 ※パスは jmeter をダウンロードしたパスに合わせて変更してください。自身の環境ではデスクトップに置いてあるので下記のパスになっています。

  • -n: これは非 GUI モードを指定します。
  • -t: これは実行するテスト計画の JMX ファイルへのパスを指定します。
  • -l: これは結果を保存する場所を指定します。この場合、結果は JTL 形式で保存されます。
~/Desktop/apache-jmeter-5.5/bin/jmeter -n -t ~/Desktop/apache-jmeter-5.5/bin/templates/google-search.jmx -l ~/Desktop/results.jtl

実行すると results.jtl ファイルが指定した箇所に生成される。結果を確認するには JMeterView Results TreeBrowse で生成されたファイルを開くことで結果を確認出来ます。

CUIで実行した結果を見る

CUI で出力された結果には Response Body にデータ等が記載されていないので注意が必要です。これは実行時の負荷を軽減するために意図的に保存しないようになっていると思われます。 なのでデフォルトではそれらの結果を保存しないようになっています。 保存して確認したい場合は下記のであらかじめ有効にしておく必要があります。 apache-jmeter-5.5 > bin フォルダ内の jmeter.properties という設定ファイルの下記の欄を true に変更することで保存されるようになります。

jmeter.save.saveservice.requestHeaders=true
jmeter.save.saveservice.responseHeaders=true
jmeter.save.saveservice.responseData=true

最後に

これで JMeter のインストールから負荷テスト実施まで一通り行う事が出来ました。実際の負荷テストでは上記の作成方法に従って、5 分間で 9000 リクエストを投げて特に問題なくレスポンスが返ってきているのを確認出来たのでよかったです。最後まで読んでいただきましてありがとうございました。

参考文献

【図解】はじめてでもわかる JMeter の使い方

「技術書」の読書術 から学ぶ効率的な読書方法。積読は避けられない

「技術書」の読書術から学ぶ効率的な読書方法
「技術書」の読書術から学ぶ効率的な読書方法
Knowledge illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です(以下「筆者」)。

いきなりですが、この記事を読んでくださっている皆さんはどのように技術の学習をされているでしょうか?

プログラミング言語やライブラリの公式チュートリアルやドキュメントだったり、技術書だったり、最近だと動画教材もありますよね。

筆者は趣味も兼ねて、毎月技術書を購入する習慣があります🤓

読書習慣があるのはいいものの、少しでも気になる書籍が出版されるとすぐに買ってしまうので、10冊以上も読んでない本が溜まっています😭

「この読めず溜まっている本を効率的に読むはどうしたらいいのだろうか...?」

と悩んでいたところ下記の書籍を発見し、学びが深かったので記事にしてみようと思います。

「技術書」の読書術
「技術書」の読書術

Amazon より引用

この記事を読む前に

想定する読者

先に述べておくと、この記事は以下のどちらかに当てはまる方向けです。

  • 技術書を同時に2冊以上読んだ経験がある方
  • 5冊以上技術書を読んだ経験がある方

まだ技術書を読み慣れていなかったり、これからはじめて技術書を読むという方向けではありません。

個人的には「『技術書』の読書術」自体もある程度、書籍を使った学習に慣れている人向けかな...と思っています。 筆者と同じ悩みを抱えている方にぜひおすすめしたい書籍です!

この書籍から学べたこと

筆者がこの書籍から学べたことは以下の3つです。

  • 積読は避けられない
  • 対象読者を見極めよ
  • サンクコストを見極めよ

結論として、本が溜まっていく状態、いわゆる積読(つんどく)は避けられず、技術書に対する見極めが肝心ということです。

これらの解説をしながら本編に参りましょう!

積読は避けられない

本が読めず溜まっていることについて、解決策がないか探して「『技術書』の読書術」を手に取ったものの、積読は避けられないということみたいです😇

なぜ積読が避けられないのか?

積読が避けられない理由は、世界中で発信される情報量と個人が読める読書量が圧倒的に違うからです。

単純な理由ですね。我々が情報を取りに行こう・学ぼうという姿勢を続ける限りはこの呪縛から逃れることはできないようです。

技術は頻繁にアップデートされる

特にIT業界だと日々技術が進歩しており、

  • 昨年買った JavaScript の書籍が今年になって新版が出る
  • React の書籍を買ったものの、最新版の仕様と違うことが書かれてる
  • 名著と呼ばれてるけど内容が古い

など、技術書の内容が役に立たなかったり、最新版の仕様と違っていて行き詰まることがあります。

技術が進歩し続ける限り、このような現象はどうしても起きてしまいますよね。

放置しておくと役に立たない情報が溜まってしまうことも積読の要因の1つです。

積読を避けられないならどうすればいいのか?

「『技術書』の読書術」の中では、書籍に対する見極めが肝心であると述べられています。

すなわち、数ある書籍の中から自分にあったものを絞って読むことが効率的な学習には大事であるということです。

書籍で語られる中で特に重要に感じた「対象読者の見極め」と「サンクコストの見極め」について解説していきます💪


対象読者を見極めよ

初級者が熟練者向けの本を読んでもさっぱり理解できなかったり、中級者が初級者向けの本を読んでもつまらなかったりしますよね。

執筆者が想定している読者層に当てはまっていないと、効率的な学習が行えず時間の無駄になってしまいます。

技術書の対象読者を確認する

具体的に書籍が提示している対象読者を確認する方法として

  • タイトルから見極める
  • 書籍のまえがきから見極める

などがあります。

タイトルから見極める方法については「入門〇〇」・「初心者が学ぶ〇〇の方法」などは当然初学者を対象としています。
「熟練〇〇」・「詳解〇〇」などのタイトルがついていると中級者以上を対象としている事が多いです。

また、書籍のまえがきには「想定する読者」などの項目が設けられており、執筆者が読者に対して持っていてほしい前提知識などが詳しく書かれていることがあります。オライリーから出ている有名な技術書にはたいてい書かれていますね。

書籍のレベルを知っても積読は止められない...

実は「『技術書』の読書術」を読む以前から対象読者の見極めというのは、筆者自身も心掛けていることでした...。

しかしながら積読状態は止められず困っていたのです。

そこで大事になるもう一つの見極めが「サンクコストの見極め」です。

サンクコストを見極めよ

ここでいうサンクコスト(直訳で埋没費用)とはすなわち、学んだものの使う機会のない知識に投じた学習時間のことです。

時間というのは戻ってくることはありません。効率的な学習を行うにはそもそもコレは今、学ぶべきことなのか?を考えることが大事です。

読むべきかを判断するために2回読め!!

対象読者に当てはまるだけの前提知識を読者が有していても、

「知りたかったのはこんなことじゃない...」

ということは発生します。サンクコストの発生を最小限に留めるために、

  1. はじめの1回は流し読みをして全体を把握する
  2. 2回目は手を動かしながら理解度を高める

というのがオススメされています。
1回目で「なんか違うな...」とか「全然わかんねぇ...」となれば、今は読む必要がないということです。

はじめに読んだ段階である程度、全体像を把握することができたのであれば、2回目で記載されているコードを動かしてみて理解度を深めるのが良いみたいですね。

サンクコストを見極めて読まなくてもいい本を避けよう

技術書をたくさん読んでいくと「これ読んだけど使うことなかったな...」と感じたことがある人も多いはずです。

筆者は、技術書を読むことは「実用的な知識を効率的に得ること」だと思っています。

2回読むのは遠回りに思えますが、サンクコストの見極めには最善の方法なのかもしれません。


最後に

最後まで読んでくださり、ありがとうございました🙇‍♂️

今回は「『技術書』の読書術」より、筆者が最も学べた「書籍を見極める方法」に焦点を当てさせていただきました。

書籍の中には、技術書を読むために役に立つティップスやアイデアがまとめられており、読む人が違えば他にも学べることがあると思います。

技術書を複数冊読んで、筆者と同じような悩みを抱えたことがある人にはオススメできる書籍です!

Amazon 「技術書」の読書術 達人が教える選び方・読み方・情報発信&共有のコツとテクニック

ドキドキ☆初めての同人誌!技書博 参加レポート!!

どうもPMの福島です。

弊社Wantedlyのストーリーなどで記事を書いたりしてますが、テックブログは「はじめまして!」ですね。簡単な自己紹介ですが、SIerの営業出身でして、コードは全く書けないBiz出身のPM*1を担当させて頂いております。

さて今回は、5月28日(日)に名古屋は吹上ホールにて開催の技術書同人誌博覧会8(以下、技書博)に参加してきましたので、簡単なレポートをさせて頂きます!

個人的には、エンジニアコミュニティに入っていった事がないし、同人誌を購入した事も無いので、若干心配しておりましたが、、、、そんな心配をよそに非常にオープンな感じでして、入場!!

技書博ロールアップバナー

技書博とは

見出しに「とは」と書いている自分も参加中は技術書の同人誌のイベントぐらいの情報しか把握しておりませんでした。 しかし、入場者に配布される公式ガイドを読む事で詳細を知ることができました。

  • 名前の通り技術書オンリーの同人誌頒布即売会。
  • 2019年に立ち上げ、イベントを通じて執筆者を増やす活動を続けていた。
  • 「技術であると信じるもの」について分野を問わず受け付けている。

入門書的なものから奥深いものまで、僕なんかでも入って行きやすい、懐深い感じのイベントです。ありがたい。

今回出展サークルの見本
そして、今回は初の遠征開催ということでした。名古屋でやってもらえたのありがたい。

ちなみに技書博がどんなイベントなのかを知れた公式ガイドも同人誌になっており、運営者やスポンサーの寄稿があり読み応え十分です。

お邪魔したサークル

参加サークル配置図

福岡市

見本が置いてある机のすぐ横に協賛団体でもある福岡市さんのブースがありました。 福岡市さんはエンジニアフレンドリーシティ福岡と銘打ち、「エンジニアが集まる、活躍する、成長する街、福岡」の実現に向け、 エンジニアカフェという交流スペースを運営されているそうです。

素晴らしい街だ。ぜひ出張したい。

Rails Girls わんだーらんど

担当しているプロダクトで使用されているRuby及びRuby on Railsは理解していきたい。と思っているので、『RubyRailsの学習ガイド 2023』と『Railsの教科書』を購入。 初めて薄い本(同人誌)を購入するという体験をこちらのブースで致しました。

Rails Girls

Mofukabur/モフカブール

インフラ系の入門書があったので「これは!」と思い、 「明後日から使えるKubarnetes入門」「明後日から使えるDocker入門」をそれぞれ購入。

明日からではなく、明後日からというワードチョイスに惹かれた気もしないでもないですが。。。

Mofukabur

おまけ

入口すぐに「パンドラ袋」なるものがございまして、

1袋2000円のパンドラ袋

マネージャーに昔言われた「開けちゃいけないから"パンドラ"なんだよ。」の一言は思い浮かんだのですが、

パンドラ袋
購入。

そして開封の儀。

パンドラ袋の戦利品

最後に

本ブログのタイトルとは裏腹にドキドキをすることなく、 入門書もありこれからエンジニアになるって方でも楽しめるイベントだと感じました。 運営の皆さん、ブース出店された皆さん、ありがとうございます。

次回は2023年11月25日東京大田区で開催するということで、お近くの方は是非!

技書博オフィシャルサイト

*1:会社によってPMの立ち位置や意味合いが異なるかと思いますが、N2iではマネジメントは階層でなく役割という考え方です。別の機会にチーム構成とかの話したいですね。

Reactを使ってtable要素に無限ローディングを実装することになった

最初に

テーブルレイアウト無限ローディング

今回、開発期間が半年ほどの新機能追加のタスクに参加して、新規画面の実装を行いました。

そこで table 要素を使って組んだレイアウトに無限ローディング機能 + ユーザが選択した要素を別テーブルで保持する機能の実装を行いました。

どのように実装したか紹介しようと思います。

仕様

まず実装前にデザイナーから Figma で作成された UI と共に以下のような要望がありました。

  1. テーブルのレイアウトが 2 つ並んでいる

  2. 左のテーブルに要素の一覧がある。クリックするとチェックボックスがONになり、右のテーブルにXアイコンと共に表示される

  3. 追加された値を消すにはXアイコンを押すか、チェックボックスをオフにする

2 つのテーブルはまさに「運命共同体だ!!」という感じでした。
これだけだと、実装するのは難しかったため、デザイナーと話し合いながら細かい仕様を決めていきました。

  • API から一覧の情報が返るので、取得した値を左のテーブルに表示
  • 一度に表示される値は 10 件まで
  • テーブルはデータ追加に伴い、一定の高さを超えるとデータをスクロールして閲覧する形にする
  • 検索結果が 10 件以上の場合は下までスクロールされた際に追加で 10 件取得
  • 1 万件までは 右のテーブルに追加されることを想定
  • 右のテーブルには多くても 10 件ほどしか追加されない
  • 取得した要素がクリックされたらチェックボックスを ON する
  • チェックボックスが ON になったら、右のテーブルにクリックした値をXマークと共に追加
  • チェックボックスを OFF にすると、右のテーブルから OFF にした値が消える
  • Xマークをクリックすると、右のテーブルからクリックした要素が消え、左テーブルにあるチェックボックスを OFF にする

こんな感じにある程度、仕様を固めて実装に入りました。

実装

業務で書いたコードをそのまま載せられないので、ポケモン API を使って似たような機能を実装しました。

リポジトリ - テーブルレイアウト無限ローディング

こちらの実装したコードを使って、解説出来たらと思います。

# clone
git clone https://github.com/wimpykid719/react-component.git

# パッケージのインストール
npm install

# アプリ起動
npm start

データ構造

無限ローディングによって、取得したデータがどんどん配列へ追加されていきます。
そのため、全てのデータを 1 つの配列で保持してしまうと、操作してデータが追加されるたびにチェックボックスの状態更新の処理が重くなっていくと考えました。

なのでデータを 2 つの配列と連想配列(オブジェクト) の 3 つに分けて保持するようにしました。

イメージとしては

// 選択可能なポケモン一覧
// 右テーブルに表示する選択肢一覧の並び順と連想配列でデータ操作する際のキーを配列で保持している
// この配列をmapしながらデータを表示
const pokemonNames = ['キー1', 'キー2', 'キー3', 'キー4'...]

// 連想配列に右テーブルで必要なデータを保持
// こうする事で選択可能なポケモンが増えていっても、データ操作はキーを使って行えるので、配列操作よりも早い
const pokemonObj = {
 キー1: {
         name: 'ポケモン名前',
         url: '詳細なURL',
         checked: false, // チェックボックスの状態
        },
 キー2: {
         name: 'ポケモン名前',
         url: '詳細なURL',
         checked: false,
        },
 キー3: {
         name: 'ポケモン名前',
         url: '詳細なURL',
         checked: false,
        },
  キー4: {
         name: 'ポケモン名前',
         url: '詳細なURL',
         checked: false,
        },
}

// 選択中のポケモン一覧
// こちらはそこまでデータが追加されない仕様のため配列で保持している
const checkedPokemons = [
 {name: 'ポケモン名前', url: '詳細なURL'},
 {name: 'ポケモン名前', url: '詳細なURL'},
 {name: 'ポケモン名前', url: '詳細なURL'},...
]

こんな感じにすることでデータ量が増えていっても、チェックボックスの状態を操作する処理はそこまで遅くならないようにしました。

テーブルレイアウト - 無限ローディング

左のテーブルレイアウト

<TableWrapper onScroll={onScroll}>
  <Table ref={tableRef}>
    <Thead>
      <Tr className="TrTh">
        <Th>ポケモン名</Th>
        <Th>詳細URL</Th>
      </Tr>
    </Thead>
    <Tbody>
      {pokemonNames.length === 0 ? (
        <Tr>
          <Td colSpan={2}>
            <NoPokemon>
              {isError
                ? "ネットワークエラー、ポケモンを取得できません"
                : "ポケモンが表示されます"}
            </NoPokemon>
          </Td>
        </Tr>
      ) : (
        pokemonNames.map((name) => (
          <SerachedTr
            className="TrTd"
            key={name}
            onClick={
              pokemonObj[name]?.checked
                ? () => removePokemon(pokemonObj[name]?.name)
                : () => addPokemon(pokemonObj[name])
            }
          >
            <Td>
              <Checkbox
                checked={!!pokemonObj[name]?.checked}
                label={pokemonObj[name]?.name || ""}
              ></Checkbox>
            </Td>
            <Td>{pokemonObj[name]?.url}</Td>
          </SerachedTr>
        ))
      )}
      {!isLoading && (
        <Tr>
          <TdLoading colSpan={2}>
            <LoaderWrapper>
              <LoadingCircle />
            </LoaderWrapper>
          </TdLoading>
        </Tr>
      )}
    </Tbody>
  </Table>
</TableWrapper>

table 要素を div 要素(TableWrapper)で囲っています。
理由としては table 要素は border-raidus が指定出来ないのでラッパーを使ってテーブルの角丸を作ることにしました。

table要素角丸

そして onScroll をラッパーに設定しました。
テーブルがスクロールされる度に指定したメソッドが発火するようになっています。

const SCROLL_HEIGHT = 576;
...

const TableWrapper = styled.div`
  overflow-x: auto;
  max-width: 432px;
  width: 100%;
  border-radius: 4px;
  border: solid 1px #ccc;
  max-height: ${SCROLL_HEIGHT}px;
  overflow-y: scroll;
  margin-top: 20px;
  @media screen and (max-width: 1279px) {
    max-width: 600px;
  }
  @media screen and (max-width: 720px) {
    min-width: 290px;
  }
`;

TableWrapper は最大の高さが 576px になっていて中の table 要素がそれ以上になる時にスクロールできるようになっています。
※要素がテーブルヘッダ + 10 件の時に 576px 超えるようになっている。

スクロール検知に使用される買う要素の高さ

const onScroll = async (e: React.UIEvent<HTMLDivElement>) => {
  if (!tableRef.current) return;
  const { scrollHeight, scrollTop, clientHeight } = e.currentTarget;
  if (
    clientHeight + scrollTop !== scrollHeight ||
    isLoading ||
    !url ||
    tableRef.current?.clientHeight < SCROLL_HEIGHT
  )
    return;
  await execFetchPokemon();
};

スクロールが検知されると上記のメソッドが実行されます。
TableWrapper の各要素の高さを取得して判定に利用しています。

  • scrollHeight(紫の範囲): 隠れてスクロールできる要素全てを足した高さ
  • clientHeight(青の範囲): 見えている要素の高さ。10 件のデータが入っている時はmax-height: 576px;を指定しているので576 になる
  • scrollTop(黄色の範囲): スクロールして上に隠れた部分の高さ。スクロールすると値が増えていく

なので clientHeight + scrollTop を足して scrollHeight と同じになる時に最下部まで要素がスクロールされたと判定することができます。この時、新たにデータを取得することで無限ローディングを実現しました。

判定の内容

  • スクロールが最下部ではない
  • ローディング中
  • url が falsy な値
  • table 要素の高さが 576px 未満

上記の際は無限ローディングが実行されないようにしました。

const execFetchPokemon = async () => {
  setIsLoading(true);
  await fetchPokemon(url);
  setIsLoading(false);
};
...

const fetchPokemon = async (url: string | undefined) => {
  try {
    if (!url) return;
    const response = await fetch(url);
    const data: PokemonFetchedData = await response.json();
    const nextUrl = data.next || undefined;
    setUrl(nextUrl);
    setPokemonObj((prePokemonObj) => {
      const newPokemonObj = pokemonSelectCheckedObj(
        data.results,
        checkedPokemons
      );
      return { ...prePokemonObj, ...newPokemonObj };
    });
    setPokemonNames((prePokemonNames) => {
      return [
        ...prePokemonNames,
        ...data.results.map((result) => result.name),
      ];
    });
  } catch {
    setIsError(true);
  }
};

取得処理が走ると state に保存された url を元に api にリクエストを投げます。受け取った値に次の url が含まれていたら state にセット。なければ undefined をセットします。

pokemonSelectCheckedObj で上で紹介したデータ構造にして、ポケモン名だけ配列も同時にステートにセットする。

ここまでが table 要素を使った無限ローディングになります。
ここから先は運命共同体チェックボックスの解説になります。なので React で無限ローディングを実装したい人はこれで実装する事ができると思います。

テーブルレイアウト - チェックボックス

チェックボックス

<SerachedTr
  className="TrTd"
  key={name}
  onClick={
    pokemonObj[name]?.checked
      ? () => removePokemon(pokemonObj[name]?.name)
      : () => addPokemon(pokemonObj[name])
  }
>
  <Td>
    <Checkbox
      checked={!!pokemonObj[name]?.checked}
      label={pokemonObj[name]?.name || ""}
    ></Checkbox>
  </Td>
  <Td>{pokemonObj[name]?.url}</Td>
</SerachedTr>

クリック出来る範囲を広くしたいので、実際にはクリック出来る要素はテーブルの行になります。
チェックのON / OFFによってクリック時に発火する 2 種類のメソッドを用意しています。

// クリックされた要素のpokemonObjのcheckをチェックONの状態にする
// 右のテーブルレイアウトに要素を追加する
const addPokemon = (pokemon: Pokemon | undefined) => {
  if (!pokemon) return;
  const pokemonName = pokemon.name;
  setPokemonObj((prePokemonObj) => {
    return {
      ...prePokemonObj,
      [pokemonName]: {
        name: prePokemonObj[pokemonName]?.name || "",
        url: prePokemonObj[pokemonName]?.url || "",
        checked: true,
      },
    };
  });
  setCheckedPokemons((preCheckedPokemons) => {
    const checkedPokemon = { name: pokemon.name, url: pokemon.url };
    if (!preCheckedPokemons) {
      return [checkedPokemon];
    } else {
      return [...preCheckedPokemons, checkedPokemon];
    }
  });
};

// クリックされた要素のpokemonObjのcheckをチェックOFFの状態にする
// 右のテーブルレイアウトから要素を削除する
const removePokemon = (pokemonName: Pokemon["name"] | undefined) => {
  if (!pokemonName) return;
  setPokemonObj((prePokemonObj) => {
    return {
      ...prePokemonObj,
      [pokemonName]: {
        name: prePokemonObj[pokemonName]?.name || "",
        url: prePokemonObj[pokemonName]?.url || "",
        checked: false,
      },
    };
  });

  setCheckedPokemons((preCheckedPokemons) => {
    return preCheckedPokemons.filter(
      (checkedPokemon) => checkedPokemon.name !== pokemonName
    );
  });
};

右のテーブルレイアウト

<TableWrapper>
  <Table>
    <Thead>
      <Tr className="TrTh">
        <Th>ポケモン名</Th>
        <Th>詳細URL</Th>
      </Tr>
    </Thead>
    <Tbody>
      {!checkedPokemons || checkedPokemons.length === 0 ? (
        <Tr>
          <Td colSpan={2}>
            <NoPokemon>選択中のポケモンが表示されます</NoPokemon>
          </Td>
        </Tr>
      ) : (
        checkedPokemons.map((checkedPokemon) => (
          <Tr className="TrTd" key={checkedPokemon.name}>
            <PokemonNameTd>
              <RemoveButton
                onClick={() => removePokemon(checkedPokemon.name)}
              />
              {checkedPokemon.name}
            </PokemonNameTd>
            <Td>{checkedPokemon.url}</Td>
          </Tr>
        ))
      )}
    </Tbody>
  </Table>
</TableWrapper>

右のテーブルには、左のテーブルでチェックボックスがONになった要素が表示されるようになっています。
RemoveButton をクリックすると removePokemon が実行されて左のテーブルのチェックボックスは OFF になり、右のテーブルから削除されるようになっています。

これで今回の仕様に沿った実装が出来ました!

最後に

無限ローディングをどうやって実装するんだろう...と最初は分からなかったのですが、なんとか実装出来てよかったです。
テーブルレイアウトに関しては苦手意識があって、はじめ既存のテーブルレイアウト(div で作られた)で実装しました。 既存のものにはレイアウト崩れがあって table 要素に書き直す事になった時は大変でした(ほとんどデザイナーの人に書いてもらった)。

その節はとても助かりました。ありがとうございます。
理解が足りていない部分もあったため、今回、自分の理解を深める目的も含めて会社のテックブログに解説記事を書いてみました。今後も工夫したコンポーネントなどを記事に出来たらと思います。

ここまで読んで頂きありがとうございました。

参考文献

Documentation - PokéAPI

Border style do not work with sticky position element

使いやすさの追求: フロントエンドに挑戦する女性エンジニアへインタビュー!

こんにちは、技術ブログ運営チームの岡部です。
今回から定期シリーズとして社内のエンジニアに自身のキャリアや仕事への取り組み方などについてインタビューをしていきたいと思います。記念すべき第1回目は香川県にお住まいのエンジニア 安田さんにお話を伺いたいと思います。

安田さんが香川に移住した時のインタビュー記事も合わせて、ぜひご覧ください。

www.wantedly.com

普段の業務について

-- まず普段、安田さんが担当されている業務、使用している言語やフレームワークについて教えてください

はい、最近はコボットのフロントエンドを担当することが多いです。
大きなタスクをガンガンやるというよりかは既存機能の改善・改修を行うことが多いです。コボットのフロントエンドはReact + TypeScriptで作られているので、主にReactを触ることが多いですね。

-- えぇ!安田さんは元々バックエンドを担当されるイメージだったので驚きです。フロントエンドをやっていて面白いのはどういった所でしょうか?

変更したものがすぐに見た目に反映されるのが面白いなぁと思います。
バックエンドと比べて変化が分かりやすいのが嬉しいです。あとはアプリケーションの見た目が変わるというのが新鮮ですね。

キャリアについて

-- 安田さんがエンジニアをやっていて良いな〜と思う所はありますか?

在宅で勤務ができるというのが嬉しいです。

安田さんは香川からリモートワークで勤務されています

あとはChatGPTなど、最先端の技術に触れて仕事ができるのが楽しいです。関係のない分野の技術もありますが、少しでも近いところで働けているのが面白いです。

パティシエは毎日、同じものを繰り返し再現するのが大事なんですが、エンジニアは毎回、新しいことに取り組むというのが難しくもあり面白い所だと感じます。

-- そういえば、安田さんの前職はパティシエでしたね。なぜエンジニアにキャリアチェンジされたのでしょうか?

パティシエは楽しかったんですが、30歳になってくると結婚したりと続けている人が周りにいなくて将来に不安があり転職を考えていました。

そんな中、なぜエンジニアを選んだのかというと兄の影響があります。兄は現在もエンジニアをしており、大学時代も情報系の学部を専攻していました。昔から自宅には最新のパソコンがあったり、ネットワークが整備されていたりしました。

ただ、兄に「エンジニアに転職するよ」と報告した時はすごく止められました(笑)

-- お兄さんの影響だったんですね(笑) 今まで知りませんでした。安田さんはどのようなエンジニアを目指しているのでしょうか?

それが、今はハッキリとしたものはないんですよね。今でこそ女性のエンジニアは増えてきましたが、まだキャリアモデルとして確立されていないと感じる所があります。そんな中で、自分もどうなりたいかを日々、探しています。

-- これから身につけたいスキルや、できるようになりたいことはありますか?

最近、フロントエンドを触る機会が多いので、もっと深く知りたいなと思っています。
特に「使いやすいデザインってどんなデザインなのか」について勉強してみたいですね。
デザイナーになりたいわけではないんですが、普段使っているアプリでも使いやすいもの・使いにくいものがあると思います。その違いって何となくでしか分からないんですよね。

ただ、デザインの本を読んでみると心理学の分野であったり、スマホだと親指が届く範囲にボタンが配置されていたりと理由があります。知れば知るほど、デザイン1つに考えが詰め込まれていて面白いなぁと思います。

-- 確かに。フロントエンドのエンジニアがUI・UXまで意識できると1つの上のレベルに行けそうです

そうですね。ボタン1つでも色や形など、考えることがたくさんあります。
N2iのデザイナーの幸地さんに「どうしてこのデザインなんですか」と質問すると、思ってもいなかった答えが返ってきて、面白いなぁと思ったことがあります。
それがデザインに興味を持ったきっかけだったかもしれません。

エンジニアになったばかりの頃

-- 少し話は変わりますが、エンジニアになったばかりの頃に苦戦したことはありますか?

もう毎日苦戦していました。みんな通る道かなと思うのですが、エラー1つ全然、解決できないことがありました。調べても調べても分からず...出来ないことをどうやって説明したら良いのかというのも結構、難しかったです。

-- その時は、どうやって乗り切ったんでしょうか?

ひたすら聞くか調べていました。
半泣きになりながら「このエラーどうにしかしてください...」という気持ちでやっていました。
気づいたら半日経っていたりしましたね(笑)

-- 1日調べても全く解決できない時ありますよね(笑) そういう時に限ってしょうもない原因だったり...。最近、苦戦されていることはあるんでしょうか?

最近は目標の設定とか自分のキャリアをどう積んでいくのかについて悩んでいることが多いです。
昔のようにエラーが解決できずに半日、悩むということはほとんどなくなったので、何も分からなくて何もできないというレベルからは抜け出したと思います。

仕事への取り組み方について

-- 作業の際に気をつけていることやこだわりポイントはありますか?

やっぱりバグが出ないように作るというのを一番に考えています。
基本的な事ですけど、仕様通りに出来ているかは注意しています。 作ってみたら要求されている仕様と違ったり...というはよくあるので、少しでも疑問に思ったことは聞くようにしています。

過去に何となくで実装を進めたら失敗したことは何度もあります。
完成してみたら「あれ仕様と違う?」となることも(笑)

-- 分かります(笑) 作ったものの「いや、そうじゃないんだよな」と言われることはしょっちゅうあります

そうですね。コミュニケーションって本当に難しい...。
最近、コードを書いている時間って本当に短いなぁとよく思います。
ほとんどが仕様確認や実装どうするかを考えている時間でコードを書くまでの時間が非常に長いです。

エンジニアがこんなにコミュニケーション必要な仕事だと思っていませんでした。
パティシエだった時よりも、コミュニケーションをとっている気がします。

-- 最後の質問です。仕事の進め方や取り組み方など安田さんが大切にしていることはあるでしょうか?

プライベートは大事にしたいなと思います。
しっかり休まないと仕事へ影響が出てくると思うんですよね。今は子供のこともあるので、限られた時間で集中して取り組んで、休む時は休むという切り替えを大事にしたいです。

まるで映画のワンシーンのような香川の海辺...😊

-- 確かにいつまでも同じような働き方ができるとは限りませんし、フェーズによって理想の働き方って変わってきますよね。

そうですね。一人の時は夜遅くまでやったりとか、土日もやったりとかしていましたが、切り替えは大事だなと改めて思います。

-- 今回は安田さんにインタビューをさせていただきました。本日はありがとうございました!

ありがとうございました!