🐣

React、TypeScriptでQiita記事検索機能作ってみた

2023/05/14に公開

主旨

React、TypeScriptの学習のためQiita記事検索機能を作ってみました(その記事をQiitaではなくZennに投稿する・・・。ZennにAPIがあれば「ReactでZenn記事検索機能作ってみた」になっていたはず!)。

この記事の対象読者

React、TypeScriptの初学者。

開発前のスキル

React、TypeScriptは本で読んだくらい。このあたりの本を読みました。

今回使用したライブラリーのバージョン

  • React: 18.2.0
  • TypeScript: 4.9.5
  • MUI: 5.12.3
  • Storybook: 7.0.8

環境構築

Storybook

https://storybook.js.org/docs/react/get-started/install/ の通り npx storybook@latest init を実行。@latestを付けずに npx storybook init を実行するとバージョン6.5がインストールされるようなので要注意です(2023年5月時点では)。なお、Storybookインストール前に create-react-app project_name --template typescript してReact、TypeScriptを導入しています。

その他

特に苦労することなく環境構築できたため割愛。

取り組みのポイント

Atomic Designを意識してみた

コンポーネントの見た目や振る舞いを統一したい思いがあったため、Atomic Designを意識しコンポーネント開発しました。ボタンなどのシンプルなコンポーネントはそれほど問題なく作れたと思うのですが、テーブルはかなり大きなコンポーネントになってしまい改善の余地がありそうです(そもそもテーブルはatoms?molecules?organisms?みたいな悩みもありました)。

コンポーネントの品質確認のためにStorybookを利用しましたが、基本Story以外にどのようなStoryを作成すべきか?が未だによくわかっていません(大半のプロパティはControlsで変更できるから基本Story 1つだけあれば十分かもと思ったりも・・・)。

コンポーネントをうまく作れれば、その後のページ開発はかなり楽な印象です。

TypeScriptでそこそこ真面目に型を定義してみた

この活動はTypeScriptの学習が目的のひとつなため、型についてそこそこ真面目に定義してみたつもりです。型引数、型の継承、合併型、交差型なども使ってみました。ただ、一部実現方法がわからずanyに逃げたところもあります。TypeScriptの型はかなり難しい印象です。

その他所感

Qiita APIが微妙・・・

認証なしの状態でもAPIを利用可能な点は有難いのですが、いくつか使いづらさを感じました。

本当は検索条件のユーザーに対して、キータイピングする毎にそれに合致するユーザーをQiita APIから取得、表示したかったのですが、GET /api/v2/usersにはフィルター機能がなく断念しました。同じ理由でタグについてもTextFieldでの実装としました。

記事を検索するGET /api/v2/itemsに関しても、いいね数ではフィルタリングできなかったり、ソートを指定できなかったりします。

次の学習の題材は別のAPIを利用することにします。

Vercelすごい!

GitHubにプッシュするだけでデプロイまで自動的にしてくれる&これが無料で使えるのはめちゃくちゃ有難いです。

GitHub Copilotすごい!

今回初めてGitHub Copilotを使ってみましたが、もうこれなしでの開発には戻れないくらいすごいです。いくつか実際の事例を交えてすごさを感じたポイントを説明します。

入力補完

以下のコードを最終的に書き上げたいと思ったとき、rows={results} まで入力しEnterキーを押すと、後は1行ずつCopilotが補完してくれるので、こちらはTabキーを押すだけでいいです。

<Table
  tableTitle="記事一覧"
  rows={results}
  columns={columns}
  selected={selected}
  setSelected={setSelected}
/>

処理内容補完

今回、検索条件のタグには複数のタグをコンマ区切り(空白を利用してもよい)で指定できるようにしたのですが、Qiita APIにその値を渡す際には空白を除去する必要がありました。そのため、空白を除去したタグを tagNamesWithoutWhiteSpace という変数に代入するつもりで変数宣言したところ、以降の処理 = tagNames?.replace(/\s+/g, ""); はCopilotが全部書いてくれました。すごい・・・。もちろん若干見当違いのサジェッションをすることもあるので、その点は注意が必要です。

export async function getArticles(props: getArticlesProps): Promise<Article[]> {
  const {
    tagNames,
    // 以下省略
  } = props;
  const tagNamesWithoutWhiteSpace = tagNames?.replace(/\s+/g, "");
  // 以下省略
}

コメント補完

次のコードの1行目に // と入力すると TODO: クエリパラメータの作り方をもう少しスマートにしたい というコメントを補完して下さりました(「お前のコードもうちょっと何とかならんか」とおっしゃられている)。他にも処理概要をコメントしてくれることもありました。

let Url = `${baseApiUrl}v2/items?page=${page}&per_page=${perPage}`;
let query = "";
if (title) {
  query += (query && "+") + `title:${title}`;
}
if (body) {
  query += (query && "+") + `body:${body}`;
}
// 以下同じような処理が続く

まとめ&次の活動予定を備忘録的に

  • React、TypeScriptは継続的に学習していきたい
  • その際、MUI、Storybook、Vercel、Copilotは必須になりそう(MUI以外のコンポーネントでもいいですが、コンポーネント自体に関心があるわけではないのでMUIでいいかなと)
  • 次はもう少しAPIの使い勝手を事前調査してから、利用するAPIを決める。以下候補
  • 今回は作れなかったメッセージ表示、グリッドレイアウトなどもコンポーネント化したい。テーブルはDataGridを使ってみたい(こちらを利用する方が抽象的なテーブルコンポーネントを作りやすい気がする)
  • フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識を事前に読んだ上でテストにも取り組みたい

アウトプット

コード

https://github.com/shoji9x9/search_components_mui

アプリケーション

https://search-components-mui.vercel.app/

Discussion