【Web API】CRUD機能のエンドポイントとリクエスト、レスポンスをどのように設計するかをざっくりまとめてみた
目次
概要
最近APIを実装することが多かったので、忘れないようにブログにまとめます。
APIとは
APIとは、外部のプログラムからあるプログラムの機能を呼び出すための手続きを定めた規約のことです。つまり、決まりごとに過ぎないのです。APIに従ってあるプログラムの機能を呼び出す短いコードをプログラムに追加することによって、プログラム上でその機能を利用することができます。APIの詳細についてはAPIドキュメントとして公開されています。そのAPIドキュメントを見ることによって、どんな機能が提供されているのか、どんな手順でこの機能を利用できるのかが分かります。
余談ですが、「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典のAPIの説明が分かりやすかったので以下に引用します。
本当は外部のプログラムとかからその機能にお仕事を依頼するための窓口の使い方とかに関する決まり事がAPIです。
例えば「お金を入れるとケーキを作る機能」のAPIであれば
・「お金を入れるとケーキを作る機能」にお仕事を依頼するための窓口は、どこにあるの?
・窓口を使うときは無言でお金を渡せばいいの?何か一声かける必要がある?
・窓口に渡すお金は現金?小切手?カード払いはいける?
・窓口から出てきたケーキは、どうやって受け取ればいいの?お皿を用意しておく必要がある?
などの使い方ルールを含みます。
ただし、一般的には、窓口そのものと捉えた方がイメージしやすいはずです。
API (エーピーアイ)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
APIと実装
APIはあくまで「外部のプログラムからあるプログラムの機能を呼び出すための手続きを定めた規約」です。つまり、決まりごとに過ぎないです。呼び出される機能を実装したプログラムが存在して、初めてAPIに挙げられた機能を利用できます。
Web APIとは
Web APIとは、HTTPプロトコルを利用してネットワーク越しにプログラムを呼び出すための手続きを定めた規約のことです。ざっくり言うとWebベースのAPIです。
Web APIにおけるエンドポイントとは
Web APIにおけるエンドポイントとは、APIにアクセスするためのURIのことです。APIは通常様々な機能がセットになっているので、複数のエンドポイントを持ちます。
リソースとは
リソースとは、何らかのデータのことであり、APIであればそのエンドポイントで取得できる情報のことです。つまり、エンドポイントは、HTTPメソッドで操作する対象( = リソース)を表していると分かります。
Web APIのエンドポイント、リクエスト、レスポンス
例として、「あるユーザーに紐づくタスクに対してのCRUD機能」をAPIで提供するケースを考えます。ドメインはsample.xyz
とします。
余談ですが、先ほどエンドポイントはリソースを表すと言いましたが、エンドポイントの複数形の部分は、あるデータの集合を表します。そして、エンドポイントのidなどは、個々のデータを表しています。 この決まりさえ分かれば、http://sample.xyz/api/users
にGETリクエストを出した場合、ユーザー一覧が取得できることも感覚的に理解できます。
GET
GETメソッドはリソースを取得する際に使用します。
タスク一覧機能
■ エンドポイント
http://sample.xyz/api/users/{id}/tasks
■レスポンス
- ステータスコードは200 OK
- レスポンスボディにユーザーに紐づくタスク一覧が格納されている
- リクエスト失敗した場合、ステータスコードは404 Not Found
- 404 Not Foundは指定したリソースが見つからないことを表す
タスク詳細機能
■ エンドポイント
http://sample.xyz/api/users/{id}/tasks/{task_id}
■レスポンス
- ステータスコードは200 OK
- 200 OKは、リクエストが成功したことを表す
- レスポンスボディにユーザーに紐づく指定したタスクが格納されている
- リクエスト失敗した場合、ステータスコードは404 Not Found
POST
POSTメソッドは、リソースを新規登録する際に使用します。POSTリクエストは、データの集合を表すエンドポイントに対して行います。成功すると、新しいデータがデータの集合配下に作成されます。そして、その新しいデータを表すエンドポイントでそのデータを取得できるようになります。
タスク登録機能
■エンドポイント
http://sample.xyz/api/users/{id}/tasks
■リクエスト
- リクエストボディにサーバー側に送るタスク情報が格納されている
■レスポンス
- ステータスコードは201 Created
- 201 Createdは、リクエストが成功し、新しいリソースが作成されたことを表す
- レスポンスボディには新規作成したタスクが格納されている。レスポンスボディに新規作成したタスクを格納することで、タスク登録機能を実行した後にフロント側でタスク詳細のAPIを叩かなくて済む
- リクエスト失敗した場合、ステータスコードは400 Bad Request
- 400 Bad Requestはリクエストが正しくないことを表す
PATCH
PATCHメソッドは、指定したリソースの一部分を更新する際に使用します。PUTメソッドは送信するデータで元々のリソースを完全に上書きしてしまうらしいです(未検証)。指定したリソースを更新したい場合はPATCHメソッドを使用しましょう。
タスク更新機能
■エンドポイント
http://sample.xyz/api/users/{id}/tasks/{task_id}
■リクエスト
- リクエストボディには、更新するデータが格納されている
■レスポンス
- ステータスコードは200 OK
- レスポンスボディには更新したデータが格納されている
- リクエスト失敗した場合、ステータスコードは400 Bad Requestまたは404 Not Found
DELETE
DELETEメソッドは、指定したリソースの削除をする際に使用します。エンドポイントで指定したリソースを削除します。
タスク削除機能
■エンドポイント
http://sample.xyz/api/users/{id}/tasks/{task_id}
■レスポンス
- ステータスコードは204 No Content
- 204 No Contentは、リクエストが成功したがレスポンスボディが空であることを表します。
- 削除した後に、もう一度削除したリソースを使うというケースはあまり考えられないため、204 No Contentにしています。
- リクエスト失敗した場合、ステータスコードは404 Not Found
まとめ
「あるユーザーに紐づくタスクに対してのCRUD機能」をAPIで提供した場合、5つ機能があるのに必要なエンドポイントは2つしかないことが分かりました。エンジニアになりたての頃、5つ機能があるから5つエンドポイントが必要なのかと勘違いしていたのですが、そんなことはないのでAPI設計するときは注意しましょう。
参考
Web API: The Good Parts | 水野 貴明 |本 | 通販 | Amazon
API(アプリケーションプログラミングインターフェース)とは - 意味をわかりやすく - IT用語辞典 e-Words
RESTful API設計におけるHTTPステータスコードの指針 - Qiita
アプリケーションプログラミングインタフェース - Wikipedia
【2022/12/21 ~ 2023/1/7】コードレビューで知ったことをざっくりまとめてみた
目次
- 目次
- 概要
- Open API
- Rails
- Rspec
- React
- propsでFactory関数を受け取らない
- TSでクラスのインスタンスを作るときに、括弧を省略しない
- モデルのインスタンスの値をハードコーディングしている場合、疑似enumを使う
- フォーマットを変換する系の関数の命名は、convertよりformatの方が良い
- モデルのインスタンスを生成しないとできない処理で、インスタンスを生成する必要がないと判断した場合、ヘルパー関数で定義する
- 各modelクラスのメンバーの型を定義する場合、モデル名 + メンバーの型名にする
- バランスを考えてDRYにする
- テーブルヘッダーのカラムにはthタグを使う
- index.tsxとindex.tsを同階層に配置しない
- TSは関数もオブジェクトなので、メンバーを持つ関数を定義することができる
- zodで文字列を必須にする場合は、min(1)をつければ良い
- 更新系の関数もuseProductで持つ
- Props は 慣習上コンポーネントの引数に対して用いる名称
- 同じpathからのインポートの場合、まとめる
- 無駄に条件を適用させる範囲を広げない
- Next.jsの環境変数で真偽値を持ちたい場合、0か1を使う
- 型にはめ込んだ憶え方をしない
- LayoutはgetLayoutパターンで適用させる
- 終わり
- 参考記事
概要
最近コードレビューを受けて知ったことをまとめます。
Open API
どんなリソースを操作しているかが分かるパスを書く
APIのパスは短ければ良いのかと思ってました。しかし、そういうわけではなく、どんなリソースを操作しているのかがひと目見たら分かるようなパスが良いそうです。
使い手の意図しない挙動が発生するようなAPIを作らない
APIの使い手にとって、挙動が想像しやすいAPIほど使いやすいです。APIの使い手が意図しない挙動を実装するのは使いずらくなるので、やめましょう。
Rails
has_manyで指定する関連モデル名は複数形にする
has_manyで指定する関連モデル名は複数形にする必要があるので、忘れないようにしましょう。
Active Recordオブジェクトの保存時に何か処理を実行したい場合、モデルにコールバックを書く
RailsにおけるコールバックはRailsドキュメントに詳細が書いてあったので引用させていただきます。
コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。
つまり、モデルにコールバックを書いておくことで、オブジェクトライフサイクルにおける特定のイベント発生時に、指定したメソッドを呼び出すことができます。 以下の例では、ブロックをafter_createコールバックとして呼ぶということをProductモデルに定義しています。
class Product < ApplicationRecord # 省略 after_create do product.update!(surveyed_at: Time.current) end end
hashid-railsのhashidをfindに渡しても検索できる
hashid-railsのhashidをfindに渡しても検索できます。そのため、find_by_hashidを使わなくても大丈夫です。
create_paramsとupdate_paramsが共通とは限らないので、どちらも定義する
将来的に保持する値が異なる可能性があるので、現状が同じだとしても、別々に定義しましょう。
Rspec
メッセージの検証は不要
場合によりますが、ステータスコードだけ分かれば良い場合、メッセージの検証は不要です。
単体取得のAPIをテストする場合、create_listで複数データを作る必要はない
Specは積み重なると、全部実行するのにそこそこ時間がかかります。そのため、節約できるところは節約しましょう。今回の場合は、単体取得のAPIをテストしたいだけなので、create_listを使わずに普通にcreateで単体データを作成すれば大丈夫です。
テストコード上で無駄に具体的な値を指定しない
テストコード上では「何か具体的な値を指定をする場合、意味があるものだ」という認識で見ます。そのため、もし意味がないなら指定しないようにしましょう。
let, let!, beforeをいつ使うのかを明文化する
beforeで作成するデータと、letで直接作成するデータの違いが、よく読み解かないと分からないため、明文化します
- なるべく let を用いて定義する
- let! を使わないとどうしようとないものに関してのみ let! を使う
- 込み入った前提データの作成があれば before で行う
React
propsでFactory関数を受け取らない
この状況の場合、Factory関数は子コンポーネントのonClickイベントにアローを使わずに関数を指定したいから作っています。つまり、子コンポーネントの都合なので、親コンポーネントでFactory関数を定義するのではなく、子コンポーネントに定義しましょう。
TSでクラスのインスタンスを作るときに、括弧を省略しない
TSでクラスのインスタンスを作るときに括弧を省略できます。しかし、分かりづらいのでしないようにしましょう。
■before
new Product
■after
new Product()
モデルのインスタンスの値をハードコーディングしている場合、疑似enumを使う
ハードコーディングを管理していくのは辛いので、擬似enumを利用して、定数を一元管理しましょう。この擬似enumはモデルが持つべき責任なので、モデルのファイルに書いておきましょう。
// 擬似enum const StorageTemperatures = { Normal: "normal", Frozen: "frozen", } as const; type StorageTemperatures = typeof StorageTemperatures[keyof typeof StorageTemperatures];
フォーマットを変換する系の関数の命名は、convertよりformatの方が良い
format~の方が意味がわかりやすいので、良いです。
モデルのインスタンスを生成しないとできない処理で、インスタンスを生成する必要がないと判断した場合、ヘルパー関数で定義する
モデルのインスタンスを生成しないとできないような処理は、モデルのインスタンスを事前に作る必要があるので面倒です。format系の関数はヘルパー関数として定義した方が使い勝手が良いです。
各modelクラスのメンバーの型を定義する場合、モデル名 + メンバーの型名にする
各modelクラスのメンバーの型を制限する型は、 モデル名 + メンバーのようにモデル名のprefixがついている方が良いです。 これはメンバーの名称の幅が広すぎる場合、名前が衝突する可能性があるためです。
バランスを考えてDRYにする
ベタ書きしすぎても辛いですし、過度にDRYにしすぎても定義元を見たりロジックを読み解かないといけなかったりするので、バランスを考えてDRYにした方が良いです。メリットが多い場合にDRYにした方が良いです。
テーブルヘッダーのカラムにはthタグを使う
テーブルヘッダーのカラムにはthタグを使うので、忘れないようにしましょう。
index.tsxとindex.tsを同階層に配置しない
パスを書くときにindex.tsxが反応しなかったりするので、やめましょう。
TSは関数もオブジェクトなので、メンバーを持つ関数を定義することができる
TSの関数はオブジェクトなので、メンバーを持つ関数を定義できます。ちなみにメンバーとは、クラスやオブジェクト(インスタンス)の持つ変数や関数などのことです。オブジェクトの持つ変数をメンバ変数、オブジェクトの持つ関数をメンバ関数と呼んだりします。言語によってはメンバ変数をプロパティ、メンバ関数をメソッドと呼んだりします。
以下のようにして、関数にメンバーを持たせることができます。
type SomeFuncWithMembers = (() => void) & { a: string; b: number; }; const some: SomeFuncWithMembers = () => void(0); some.a = "aaa"; some.b = 1; console.log(some.a); // => "aaa" console.log(some.b); // => 1
Reactコンポーネントは関数なので、コンポーネントをメンバーに持つコンポーネントを定義することができます。こうすることで、名前空間の衝突を防ぐことができます。テーブルコンポーネントなどの割と名前が衝突しやすいコンポーネントで行うと良いです。
zodで文字列を必須にする場合は、min(1)をつければ良い
zodで文字列を必須にする場合は、min(1)をつけましょう。
更新系の関数もuseProductで持つ
セットにすることでupdate後のstateの更新がやりやすいです、また、更新だけを単独で走らせたいケースはあんまりなく、大体get処理とセットになるためです。そのため、更新系の関数を返すuseUpdateProductを定義する必要はないです。更新系の関数もuseProductで返すようにしましょう。
Props は 慣習上コンポーネントの引数に対して用いる名称
Props は 慣習上コンポーネントの引数に対して用いる名称です。関数の引数の型などに、むやみにPropsと命名しないようにしましょう。
同じpathからのインポートの場合、まとめる
同じpathからのインポートの場合、まとめるようにしましょう。
無駄に条件を適用させる範囲を広げない
フォームコンポーネントの表示をある条件で制御したいのに、フォームコンポーネントの外側のJSXも含めて非表示にする必要はないです。指定した条件で、くくる範囲を広くしすぎないようにしましょう。
Next.jsの環境変数で真偽値を持ちたい場合、0か1を使う
Next.jsの環境変数は文字列しか使えません。そのため、基本的には0か1を使うようにしましょう。
型にはめ込んだ憶え方をしない
三項演算子を使うのか、ifを使うのか、switchを使うのか、論理和演算子を使うのかみたいな事は、「ケースバイケース」です。 そのため、「こういう時はこう書く」ではなく、都度「どう書くのが自然なのか」「どう書くのが一番問題が起きづらいのか」ということを考えながらコーディングできると良いです。
LayoutはgetLayoutパターンで適用させる
LayoutはgetLayoutパターンを使うことで、Layoutの状態を保持しつつページコンポーネントに動的にLayoutを適用させることができます。
終わり
やっぱフロントの指摘が多いなと感じました。バックエンドのタスクはスピーディーにできるようになってきたが、フロントはまだ指摘が多いので、フロントもスピーディーにできるようになりたいなと感じました。
参考記事
Active Record の関連付け - Railsガイド
Active Record コールバック - Railsガイド
【2022/12/3 ~ 2022/12/20】コードレビューで知ったことをざっくりまとめてみた
目次
- 目次
- 概要
- Open API
- Rails
- Rspec
- React
- 無駄に改行しすぎない
- z-50などの使う側の都合で決まる情報をコンポーネント自身に指定しない
- 複数のプロパティが存在する場合、型定義はインラインで行わない
- 空白を作りたい場合、cssでマージンを設定するか、flexのgapを利用する
- Open API Generatorで生成したenumは特殊なので使用しない
- React Hook Formで同じようなデータは配列で管理する
- 細かすぎる粒度でコンポーネントを切らない
- Flagmentだけをレスポンスするのではなくnull なりをreturnする
- コンポーネント内で定義する関数は全てuseCallbackをつける
- クリックしたデータをDOMから参照せずに、onClickに動的生成した関数を渡して取得する
- import時にindexを指定しない(indexは省略できる)
- フォームを作る際はバックエンド側のデータの持ち方になるべく準拠する
- まとめてエクスポートする場合、index.tsを使う
- 相対パスで短く指定できるなら、そうする
- zodのバリデーション対象が配列の場合、z.arrayを先に書く
- tailwindでdisabledを指定する場合、大元の汎用コンポーネントに指定する
- mutate関数を自前で用意するのではなく、useMutationから分割代入で取得する
- いろんなデータ操作系のhooksを同時に利用したい場合はほとんどないので、そういう時にだけ呼び出し側でmutate関数に別名をつければ良い
- typeを使った型はコンポーネントの外側で定義する
- onSubmitにはvalidation済みのデータを渡すのが前提
- valuesAsNumberはControllerだと使用できない
- enumで型定義する場合、共用体型を使う
- モデルにimmutableの設定を書く
- setStateでprevを使わない場合、書かなくて良い
- 目的に対してかなり複雑な型定義をしない
- 定義元を見に行かないとどんなデータが渡されていたり、検証されているか分からない場合、ベタ書きした方が良い。
- オブジェクトのプロパティの型を追加したい場合、交差型を使う
- APIのレスポンスのプロパティの型が必須になっていない場合、undefinedと型推論されてしまう
- setStateなどのセッターをpropsで渡す実装は避けた方が良い
- コンポーネントを切る際、コンポーネント名のディレクトリを作成してその配下にindex.tsxを作成する
- カスタムフックで何でもかんでもまとめすぎない
- カスタムフックを呼び出す際にonSuccessを渡すのではなく、カスタムフックが返すmutate関数を呼び出す際にonSuccessを渡すようにする
- カスタムフックのユースケースを制限するようなロジックをカスタムフックに含めない
- React Hook FormのvaluAsDateを利用して、Dateオブジェクトのインスタンスを作成する
- 子コンポーネント内で考慮すべきではないロジックを子コンポーネントに含めない
- 関数で1行の処理で実行結果だけをreturnする場合、アローの隣にリターンする値を書く
- APIから取得したデータをそのまま使うのではなく、フロント側で定義したモデルを使ってインスタンスを作成する
- フォームの値をそのまま投げたい
- 複数のスプレッド構文を使っている場合、スプレッド構文の順番に気をつける
- bool値の変数の命名に気をつける
- NaNはnumber型
- オブジェクトをスプレッド構文でpropsとして展開するのをやめる
- mutate関数の引数をオブジェクトリテラルでひとまとめにしない
- Propsにアロー関数を渡さない
- 終わり
- 参考記事
概要
フロントエンドのタスクをレビューしていただいた際に、知ったことをまとめます。
Open API
OpenAPI 3.1未満の場合はnullable: trueを追加しないとnullが許容されない
OpenAPI 3.1未満の場合はnullable: trueを追加しないとnullが許容されないため、nullable: trueを追加します、stoplight上でnullの表示を消しても良いのですが、分かりやすさのため、APIドキュメントにもnullを書きます。
Rails
Like句の後ろに半角スペースを入れる
Like句の後ろには半角スペースを入れます。
products = Product.where("sku LIKE ? ", "#{params[:sku]}%")
procよりラムダを使う
ラムダの方が記述が楽なので、procをどうしても使わないといけない場面でなければ、ラムダを使います。
分類を表すカラム名はkind や categoryのような単語を使う
categoryは使いやすいので、kindをまずは優先して使いましょう。
Rubyではスネークケースを使う
Rubyではスネークケースを使うので、変数定義する時に間違えないように気をつけましょう。
Rspec
存在しない値でリクエストを出したい場合、もしかしたら存在しそうな値を使わない
let(:id) { Faker::Number.number(digits: 10).to_s }
存在しない値でリクエストを出したい場合、以上のコードだとDBに存在するような値が生成される可能性があります。以下のように絶対に存在しない値(桁数を99桁)にするなどして、テストの信頼性を高めるようにします。
let(:id) { Faker::Number.number(digits: 99).to_s }
無駄にクエリ発行させない
以下のコードではskuを取得するために、pluckでクエリを一回発行しています。
before do create_list(:product, 3) end let(:sku) { product.pluck(:sku).sample }
しかし、before doの部分でデータを事前に作っているので、そのデータを利用してskuを取得した方が良いです。
before do @products = create_list(:product, 3) end let(:sku) { @products[0][:sku] }
要素数を検証したい場合、 have_attributeを利用する
配列の要素数を検証したい場合、have_attributeを利用します。
expect(response.body).to be_json_including(data: { products: have_attributes(:count, 0) })
React
無駄に改行しすぎない
無駄に改行することでファイルの行数が増えてしまうので、1行で行けるなら、1行で書きましょう。
z-50などの使う側の都合で決まる情報をコンポーネント自身に指定しない
コンポーネントの責務は基本的には、自分自身に関することです。z-50などは、コンポーネントを使う側の都合であって、それをコンポーネント自身に指定すると、そのコンポーネントを使う側の都合で決まる情報をコンポーネントが持ってしまい、再利用性が下がります(なんで使う側の都合で決まる情報を子コンポーネントが持たないといけないのか)。そして親コンポーネントの情報を持ってしまっているため、コンポーネント同士が独立していないです。そもそもReactでは複数の機能単位で分割された独立性の高いコンポーネントを組み合わせてソフトウェアを開発していきます。その際、1つのコンポーネントがさまざまな責務を背負っていたり、他のコンポーネントに依存していると、1つの責務を変更するたびに他の責務に影響がないかを細かく確認しないといけなかったり、依存しているせいで再利用性が下がる可能性があります(同じような機能をもう一度作る可能性もあるので)。1回限りしか使わないコンポーネントが増えていく未来しか見えず、同じようなコードが量産される原因になるので、独立したコンポーネントを書くように気をつけましょう。こういう親の都合で決まる値はpropsで子コンポーネントに渡します。そうすることで、親に値の情報を持たせつつ、子コンポーネントに渡すことができます。
複数のプロパティが存在する場合、型定義はインラインで行わない
複数のプロパティが存在する場合、型定義をインラインで行うと、見通しが悪くなります。そのため、型エイリアスで定義します。
■before
const kind: { value: "food" | "dailyNecessities"; label: string; checked: boolean; } = { // 省略
■after
type Kind = { value: "food" | "dailyNecessities"; label: string; checked: boolean; }; const kind: Kind = { // 省略
空白を作りたい場合、cssでマージンを設定するか、flexのgapを利用する
特殊文字の空白を使うと意図が分かりづらいので、cssで指定した方が良いです。
■before
<div> {`${product.sku}\u00A0\u00A0${product.name}`} </div>
flexのgapを使うことで、フレックスアイテム間にスペースを作ることができます。以下の場合、x方向にスペースを作っています。
■after
<div className="flex gap-x-4"> <div>{sku}</div> <div>{name}</div> </div>
Open API Generatorで生成したenumは特殊なので使用しない
自分で定義したenumを使用しましょう。理由が明確にはわかっていないので、今度遭遇した時に調べてみます。
React Hook Formで同じようなデータは配列で管理する
useFieldArrayを使うことで、配列データの要素の追加や削除、管理がしやすくなります。
細かすぎる粒度でコンポーネントを切らない
- この責務はこのコンポーネントが持つべきではない
- この情報はこのコンポーネントに含めたくないし持つべきではない
- このコンポーネントを切ることで再利用できそう
- このコンポーネントを切っても再利用する機会がなさそうなので、無駄に切らない方が良さそう
- このコンポーネントを切る価値はあるのか
上記のようなコンポーネントを切るべき明確な理由を考えずに、細かすぎる粒度でコンポーネントを切らない方が良いです。コンポーネントの粒度が細かすぎると、その分コンポーネントのファイルが増えるので、複数のファイルを行ったり来たりして開発効率が落ちる未来が見えます。デメリットしかなくメリットがないのに、コンポーネントを切るのはやめましょう。
下記の記事でその内容が書かれていて分かりやすいです。
極端なパターンを考えて、技術的な落とし所を探っていく - mizdra's blog
Flagmentだけをレスポンスするのではなくnull なりをreturnする
React コンポーネントで何も返したくない場合、Flagmentではなくnullを返します。Flagmentはそもそも、JSXで最上位に複数の要素を配置できなくて、仕方なく1つの要素を配置していたものを解決するためのものです。Flagmentを使うことで、仕方なく配置していた1つの要素を指定しなくて良くなり、かつ余分なDOMが出力されません。Flagmentもそうですが、本来の目的で使うようにしましょう。
コンポーネント内で定義する関数は全てuseCallbackをつける
useCallbackとは、関数自体をメモ化するフックです。親コンポーネントで関数を定義していて関数を子コンポーネントにpropsとして渡している場合、親コンポーネントが再レンダリングされるたびに関数が再生成されるので、子コンポーネントに違う関数をpropsで渡していると判定されてしまいます。その結果、子コンポーネントが再レンダリングします。関数自体をメモ化することで、関数の再生成を防ぎ、子コンポーネントの再レンダリングを防ぐことができます。
クリックしたデータをDOMから参照せずに、onClickに動的生成した関数を渡して取得する
以下のコードでは、DOMを参照してデータを取得しているので、コードが複雑になりがちです。そして分かりづらいです。
■before
const onClick = (e: MouseEvent<HTMLLIElement>) => { const clieckedSku = e.currentTarget?.firstChild?.firstChild?.textContent; // 省略
カリー化を使うことで動的に関数を生成して、その動的関数をonClickに渡すことができます。onClickにデータを渡したFactory関数呼び出しを指定することで、動的生成した関数にデータを渡すことができます。そしてその動的生成された関数自体がreturnされて、onClickにセットされます。
■after
const onClickFactory = useCallback((product: Product) => () => { // 本来したい処理 }, []); // 使う場所 Products.map((product) => <li onClick={onClickFactory(product)}>{product.name}</li>;
カリー化を使わなくてもかけますが、同じ関数を使う場面でも無駄に() =>
を書かないといけないかつ、Propsにアロー関数を直接渡すのは推奨されていない(useCallbackを使った関数を事前に定義する)ので、カリー化を使った方が良いでしょう。
const onClick = useCallback((product: Product) => { // 本来したい処理 }, []); // 使う場所 Products.map((product) => <li onClick={() => onClick(product)}>{product.name}</li>;
ちなみにFactoryという名前はFactoryパターンから来ています。関数を生成するという意味でFactoryを使っていると思いますが、Factoryパターンをまだあまりわかっていないので、必要になったら調べます。
import時にindexを指定しない(indexは省略できる)
indexという名前は特殊扱いされていて、components/Todo/index
と書かなくても、components/Todo
と書けばOKです。
フォームを作る際はバックエンド側のデータの持ち方になるべく準拠する
バックエンド側でのデータの持ち方をフロントエンド用に整形して扱うのはめんどくさいです。その場合、バックエンド側でのデータの持ち方が不適切な可能性があります。
まとめてエクスポートする場合、index.tsを使う
index.tsに以下のような記述をすることで、コンポーネントをまとめてエクスポートすることができます。
export * from "./Todo"; export * from "./TodoList";
相対パスで短く指定できるなら、そうする
相対パスで短く指定できる場合、そうした方が良いです。パスも短くなり可読性が上がります。
import { DefaultValues } from "./";
zodのバリデーション対象が配列の場合、z.arrayを先に書く
zodでは配列を検証する方法が2つ存在します。
const stringArray = z.array(z.string()); // => string[] // equivalent const stringArray = z.string().array(); // => string[]
配列であることを検証する場合、先に書いてある方が、シンプルで分かりやすいです。
{ productLogs: z.array(z.object({ ... })), }
tailwindでdisabledを指定する場合、大元の汎用コンポーネントに指定する
スタイルを統一させたいためです。ちなみにtailwindでは以下のようにしてdisabled時のスタイルを当てることができます。
disabled:opacity-60
mutate関数を自前で用意するのではなく、useMutationから分割代入で取得する
react queryが自前で用意しないでいいようにしてくれているので、自前で作る必要はないです。
■before
const create = useCallback(({ data, onSuccess, onError, }: { data: Data, onSuccess: ({ data }: { data: PostProducts201ResponseAllOfData }) => void onError: () => void }) => { mutate.mutate(data, { onSuccess, onError }); // 省略
■after
const { mutate: create } = useMutation((data: Data) => { return api.postProducts(data); });
いろんなデータ操作系のhooksを同時に利用したい場合はほとんどないので、そういう時にだけ呼び出し側でmutate関数に別名をつければ良い
データ操作系のhooksを定義するとき、mutate関数にcreateProduct
、createProductLog ~
と名前をつけていました。そのような名前をつけるデメリットは、hooksを使うときにその名前で使わないといけないところです。名前が長いので、書くのが面倒です。1つのコンポーネントでデータ操作系のhooksを同時に呼び出すことはほとんどないので、基本mutatet関数の名前はcreateにしつつ、もし複数のデータ操作系のhooksが必要になった場合、呼び出し側で別名をつければOKです。
typeを使った型はコンポーネントの外側で定義する
コンポーネントの中で型定義すると、エクスポートできません。また、コンポーネント自体が型の情報を持ってしまうので、責務が分離できていない状態です。
onSubmitにはvalidation済みのデータを渡すのが前提
React Hook FormではonSubmitを呼び出す前にバリデーション処理が実行されます。そのため、onSubmit内でバリデーション処理を行うのは不適切です。
valuesAsNumberはControllerだと使用できない
input numberで入力しても、zodでバリデーションをかけると値が文字列として認識されていることが分かります。valuesAsNumberは文字列をnumberに変えるためのオプションです。Controllerだと使用できないので、その場合はregisterを使った方が良いです。
enumで型定義する場合、共用体型を使う
TypeScriptではenumを使うことができます。しかし、enumには昔から問題があるので、型を定義する目的であれば共用体型を使用しましょう。
type Kind = "food" | "dailyNecessities"
ちなみにenumとは、複数の定数を一つにまとめて定義したり管理したりすることができるものです。もしenumを使う場合、以下のように疑似enumを定義しましょう。疑似enumを使うことで定数の一元管理もできて、ハードコーディングを防ぐことができます。
const Kind = { Food: "food", DailyNecessities: "dailyNecessities", } as const; type Kind = typeof Kind[keyof typeof Kind] // => type Kind = "food" | "dailyNecessities" console.log(Kind.Food); // => food
モデルにimmutableの設定を書く
immerというjsのライブラリを使うことで、ミュータブルなコードをイミュータブルにしてくれるそうです。未検証なので、もし実装する際はやってみようと思います。
export class Product implements Omit<api.Product, "storageTemperature" | "kind"> { [immerable] = true; // 省略
setStateでprevを使わない場合、書かなくて良い
setStateでprevを使わない場合、書かなくて大丈夫です。
■before
setState((prev) => [])
■after
setState([])
目的に対してかなり複雑な型定義をしない
複雑な型を定義することで、可読性が下がるので、目的に対して本当にこんな複雑な型を定義すべきなのかを考えた方が良いです。
定義元を見に行かないとどんなデータが渡されていたり、検証されているか分からない場合、ベタ書きした方が良い。
定義元を見に行くのがめんどくさいので、そのような場合はベタ書きした方が良いです。
オブジェクトのプロパティの型を追加したい場合、交差型を使う
交差型とは、&演算子を使って表現する型です。交差型を使うことで、柔軟にオブジェクトのプロパティの型を追加できます。
type User = { name: string; }; type UserWithAge = User & { age: number; }; const user: UserWithAge = { name: "yuki", age: 25, };
APIのレスポンスのプロパティの型が必須になっていない場合、undefinedと型推論されてしまう
APIのレスポンスのプロパティの型が必須になっていない場合、undefinedと型推論されてしまいます。そのため、APIドキュメントを修正して必須にしておきましょう。そうすることで、undefinedを考慮せずに済みます。
setStateなどのセッターをpropsで渡す実装は避けた方が良い
親コンポーネントで定義したsetStateなどのセッターを、子コンポーネントにpropsで渡す実装は避けた方が良いです。
■NG
// 子コンポーネント export const SearchCandidates: FC<Props> = ({ products, setProducts, setProduct, }) => {
NGな理由
- そもそもコンポーネントは自分自身にしか興味がないです。それなのになんで親コンポーネントからsetStateを受け取って、それで親コンポーネントのstateを更新してやらなきゃいけないのか?setStateを使ってどのようにstateを更新するかは親コンポーネントが持つべき情報であって、子コンポーネントが持つべきではないです。適切な場所に適切なコードを書くことによって、コンポーネント同士が密結合にならずに独立性を担保できます。独立性を担保することで、責務が適切に分離できていて変更に強くなったり、可読性やコンポーネントの再利用性が上がります。
- useStateを定義したコンポーネント内で、どのようにstateが更新されるのかを把握することができないです。子コンポーネントを見に行かないといけないので、面倒です。
■OK
export const SearchCandidates: FC<Props> = ({ products, onSelected, }) => {
OKな理由
- useStateを定義したコンポーネント内で、どのようにstateが更新されるのか管理できる
- 親コンポーネントと子コンポーネントの責務を分離できます。子コンポーネントは親コンポーネントから渡された関数を呼び出すだけであって、どのようにstateを更新するかに関して子コンポーネントが責務を持たなくて良くなります(どのように更新されるかは親コンポーネントが関心を持つべきであって、子コンポーネントにどのように更新すべきかを書くべきではないからです)。これで、SearchCandidatesの責務は①受け取ったデータを表示するのと、②そのデータを選択できるだけになります。選択後どのような処理をしたいかは、SearchCandidatesを使うコンポーネント側の責務であり、使う側のコンポーネントに書くべき情報です。以上のOKパターンだと子コンポーネントに親コンポーネントの都合で決まる情報が書かれていないので、正しく責務が分離されていることが分かります。
コンポーネントを切る際、コンポーネント名のディレクトリを作成してその配下にindex.tsxを作成する
コンポーネントを切る方法は2つ存在します。
2番の方法はコンポーネント名のディレクトリにそのコンポーネントのテストをまとめることができるので、何かと嬉しいです。インポートする際もindex.tsxなので、パスを変えずにインポートできます(つまり、どちらも同じパスでインポートできます)。コンポーネントを切る場合は、どちらかの方法に統一しましょう。
カスタムフックで何でもかんでもまとめすぎない
そもそもカスタムフックとは、コンポーネントからロジックを抽出して再利用可能にした関数です。以下のuseProductsのようなカスタムフックの場合、名前から想像してAPIリクエストを発行してproductsおよびloadingを返すと考えられます。しかし、このuseProductsの返却値としてonSubmitやdisabledなど、使う側が想定できない値を返すのは良くありません。そして、onSubmitやdisabledなどはそのコンポーネントの都合で定義するものであって、カスタムフックに含めてしまうと、カスタムフックがそのコンポーネントに依存してしまい、カスタムフック自体の再利用性が落ちます。適切にカスタムフックを切りつつ、コンポーネントの都合で定義するものはコンポーネント内に定義しましょう。コンポーネントからロジックを分離しようとして、カスタムフックで何でもかんでもまとめるのではなく、①再利用性があるのか、②使う側にとって自然か、違和感がないかを考えてカスタムフックを作成しましょう。
■before
const { products, disabled, onSubmit } = useProducts();
■after
const { products } = useProducts(); const disabled = // 省略 const onSubmit = // 省略
カスタムフックを呼び出す際にonSuccessを渡すのではなく、カスタムフックが返すmutate関数を呼び出す際にonSuccessを渡すようにする
カスタムフックを呼び出す際にonSuccessを渡すのはやめましょう。メソッドを呼び出す際にonSuccessやonErrorを指定する方が違和感がないためです。また、現状、使う人が想定できないインターフェースになっています。「なぜカスタムフックを呼び出す際にonSuccessを渡さないといけないのか?」を定義元を見ないと分からず、使いづらいです。useMutationのmutate関数は呼び出す際にonSuccessやonErrorを指定できるようになっています。そのため、mutate関数を呼び出す際にonSuccessやonErrorを指定すればOKです。
■before
type Props = { onSuccess?: (data: PostProducts201Response) => void; onError?: () => void; } export const useCreateProduct = ({ onSuccess, onError }: Props) => { const api = // 省略 const { mutate: create } = useMutation( (data: Data) => { return api.postProducts(data); }, { onSuccess, onError, }, ); return { create, }; };
■after
export const useCreateProduct = () => { const api = // 省略 const { mutate: create } = useMutation( (data: Data) => { return api.postProducts(data); }, ); return { create, }; };
カスタムフックのユースケースを制限するようなロジックをカスタムフックに含めない
ユースケースを制限するようなロジックは、大体カスタムフックを使う側の都合で定義されるロジックなので、そういうロジックはカスタムフックを使う側のコンポーネントに直接書いて、カスタムフックのユースケースを制限しないように気をつけましょう。
React Hook FormのvaluAsDateを利用して、Dateオブジェクトのインスタンスを作成する
valueAsDateを使うことで、ユーザーが入力した日付をDateオブジェクトのインスタンスに変換できます。APIリクエスト出す際にDateオブジェクトのインスタンスに自前で変換するのではなく、valueAsDateでユーザーが入力した瞬間に自動で変換するようにしましょう。
子コンポーネント内で考慮すべきではないロジックを子コンポーネントに含めない
子コンポーネント内で考慮すべきではないロジック(親コンポーネントの都合で決まるロジックなど)を子コンポーネントに含めると、子コンポーネントと親コンポーネントが依存してしまい、子コンポーネントの再利用性が下がります。そして、親コンポーネントが持つべき情報を子コンポーネントが持ってしまっているので、責務が正しく分離されていません。責務が正しく分離されていないと、子コンポーネント内で複雑なロジックを描く必要性も出てくるので、責務は適切に分離した方が良いです。
関数で1行の処理で実行結果だけをreturnする場合、アローの隣にリターンする値を書く
■before
const { mutate: create } = useMutation( (data: Data) => { return api.postProducts(data); }, );
■after
const { mutate: create } = useMutation( (data: Data) => api.postProducts(data) );
APIから取得したデータをそのまま使うのではなく、フロント側で定義したモデルを使ってインスタンスを作成する
そもそもモデルとはなんなのかwikipediaで見てみましょう。
そのアプリケーションが扱う領域のデータと手続き(ビジネスロジック - ショッピングの合計額や送料を計算するなど)を表現する要素である。また、データの変更をビューに通知するのもモデルの責任である(モデルの変更を通知するのにObserver パターンが用いられることもある)。
フロントエンドのアプリケーションでAPIから取得したデータをそのまま使うと柔軟性が低いです。フロントエンドのアプリケーションなのにデータがAPIの型に依存しています。フロントエンドではフロントエンドに特化したデータの持ち方をしたいので、フロントエンドでもモデルを使用します。このモデルも基本的にはAPIの型に準拠するのですが、モデルを作ることによって、「データ」と「データに関する操作」をまとめられます。そしてフロントエンドに特化したデータの持ち方もできます(プロパティを増やしたり)。モデルを使わない場合、無秩序に色々な場所に「データ」と「データに関する操作」が定義されていて管理しづらいかつ変更に弱いです。
以下ではAPIのレスポンスをモデルを通してインスタンス化しています。こうすることで、フロントエンドのアプリケーションでデータとデータに関する操作を一元管理できます。
■before
const { data, isLoading } = useQuery(["products"], () => api.getProducts());
■after
const { isLoading } = useQuery(["products"], () => api.getProducts(), { onSuccess: ({ data: { products } }) => { setProducts(products.map((product) => new Product(product))); } });
↓ フロントエンドにおけるモデル
export class Product implements api.Product { [immerable] = true; id = ""; name = ""; nameEn = ""; sku = ""; constructor(data: Partial<Product> = {}) { Object.assign(this, data); } }
フォームの値をそのまま投げたい
onSubmit関数の中で、フォームの値を整形して、APIリクエスト出すのはめんどくさいかつ複雑になってしまうので、onSubmitの中でフォームの値をなるべく整形しないでAPIリクエスト出せるようにInput要素にvalueAsNumberをつけたり、zodのtransformで整形しましょう。
複数のスプレッド構文を使っている場合、スプレッド構文の順番に気をつける
スプレッド構文の順番が違うだけで、プロパティの値が変わるので気をつけましょう。
const originalUser = { name: "yuki" }; const defaultUser = { name: "hoge" }; const userA = { ...originalUser, ...defaultUser, }; // => { "name": "hoge" } const userB = { ...defaultUser, ...originalUser, }; // => { "name": "yuki" }
bool値の変数の命名に気をつける
純粋に ~ かどうかを判定したい場合、is~で良いですが、~を表示したいかどうかを制御する場合は、show~の方が使う側がわかりやすいです。使う側にとって意味が分かりやすい命名をつけましょう。
NaNはnumber型
NaNはnumber型です。そのため、zodで以下のようにnumberとNaNのunion型にしても、priceはnumber型として考慮されます(型エイリアスでも同様です)。input numberで未入力を許容したいかつ値をnumber型として扱いたい場合、numberとNaNのunion型にすれば挙動を満たせるのでオススメです。初見の人には分かりづらいので、もしする場合はコメントを残しておくと良いでしょう。
↓ 型エイリアス
type Price = number | typeof NaN; // => type Price = number
// NOTE: 8個の商品で8個未満の商品を登録するために、デフォルト値のNaNを許容している price: z.union([z.number().positive(), z.nan()]),
オブジェクトをスプレッド構文でpropsとして展開するのをやめる
props名が変数名に引きづられてしまい、props名を独自に定義するときに面倒なので、普通に書きます。普通に書いた方が自然です。
■before
<Product {...{name, age}} />
■after
<Product name={name} age={age} />
mutate関数の引数をオブジェクトリテラルでひとまとめにしない
関数を使う際に、引数をオブジェクトリテラルで囲むのは面倒であり、自然ではないです。なぜオブジェクトリテラルで囲むのかを定義元をみないといけないので、それも面倒です。
Propsにアロー関数を渡さない
Propsにアロー関数を渡さない方が良いのは、公式で推奨されています。
レンダー内でアロー関数を利用するとコンポーネントがレンダーされるたびに新しい関数が作成されるため、厳密な一致による比較に基づいた最適化が動作しなくなる可能性があります。
Propsにアロー関数を渡すことで、親コンポーネントが再レンダリングされた際にアロー関数が再生成されてしまい、その結果、propsが異なるので子コンポーネントが再レンダリングしてしまいます。不必要に子コンポーネントが再レンダリングしてしまうので、パフォーマンスが悪いです。Propsにはアロー関数を渡さず、事前に useCallback などを使用して関数を定義して、その関数を渡すようにしましょう。
return ( <Todo onSelected={(item: Item) => setState( // 省略 )} /> )
const onSelected = useCallback((item: Item) => setState( // 省略 ), []) return ( <Todo onSelected={onSelected} /> )
終わり
単一責任の原則について調べていたら、wikipediaに「責務とは変更する理由である」と書いていて、すごくしっくりきたなと感じました。その言葉を知ってから、「なぜセッターを渡さなかったら責務が分離できるのか」を考えるとすごくしっくりくるなと感じました。
参考記事
コンポーネント(コンポーネントウェア)とは何か?コンポーネント指向や、その元になった「分割統治法」や「単一責任の原則」も含めて解説 | Promapedia(プロマペディア)
gapの余白指定が便利! gridとflexでできる新しいCSSレイアウト手法 - ICS MEDIA
useFieldArray | React Hook Form - Simple React forms validation
TypescriptのEnum型の代わりにUnion型を使用する
JavaScript | Dateオブジェクトのインスタンスを作成する
【TS】Null合体演算子(??)についてざっくりまとめてみた
目次
背景
Null合体演算子を実務で使うことがあったのですが、パッと使うことができなかったので、ブログにまとめようと思います。
Null 合体演算子とは
Null合体演算子(??
)とは、論理演算子の一種です。TSで代表的な論理演算子は論理積演算子(&&
)や論理和演算子(||
)です。Null合体演算子は、基本的にはNull合体演算子の左辺の値を返しますが、左辺の値がnull
またはundefined
の場合に右辺の値を返します。
■defaultAgeがnullの場合
const originalDefaultAge = 25; const defaultAge = null; const user = { name: "yuki", age: defaultAge ?? originalDefaultAge, }; console.log(user); // => { // "name": "yuki", // "age": 25 // }
■defaultAgeがundefinedの場合
const originalDefaultAge = 25; const defaultAge = undefined; const user = { name: "yuki", age: defaultAge ?? originalDefaultAge, }; console.log(user); // => { // "name": "yuki", // "age": 25 // }
■defaultAgeが30の場合
const originalDefaultAge = 25; const defaultAge = 30; const user = { name: "yuki", age: defaultAge ?? originalDefaultAge, }; console.log(user); // => { // "name": "yuki", // "age": 30 // }
Null合体演算子を三項演算子で書く場合、記述が冗長になってしまうので、Null合体演算子で書ける場合は、Null合体演算子を使用した方が良いです。
const originalDefaultAge = 25; const defaultAge = undefined; const user = { name: "yuki", // Null合体演算子を使用する場合、defaultAge ?? originalDefaultAgeと書ける age: (defaultAge === null) || (defaultAge === undefined) ? originalDefaultAge : defaultAge, }; console.log(user); // => { // "name": "yuki", // "age": 25 // }
論理和演算子との違い
Null合体演算子は、論理和演算子の特殊形とみなすことができます。論理和演算子は、左辺の値がnull
やundefined
に関わらず、falsyな値の場合に、右辺の値を返します。
参考記事
【Ruby】【TS】文字列を整数、整数を文字列に変換する方法をざっくりまとめてみた
目次
背景
文字列を整数、整数を文字列に変換する方法がパッと出なかったので、ブログにまとめようと思います。
文字列を整数に変換する
Ruby
Rubyで文字列を整数に変換するためには、String#to_i
を使用します。
age = "25" p age.to_i; #=> 25
数字が含まれていない文字列オブジェクトにString#to_i
を使用した場合、全て数字の0
に変換します。
age = "あ" p age.to_i; #=> 0
TS
TSで文字列を整数に変換するためには、parseInt()
またはNumberコンストラクタ
を使用します。
parseInt()
の場合、数値を含む文字列から数字部分だけを取り出して文字列に変換することができるので、数値以外の余計な文字列が含まれた文字列を許容しています。Numberコンストラクタ
の場合、そのような文字列をNaN
に変換するので、数値のみの文字列がちゃんと渡されていないことがわかります。そのため、 TSで文字列を整数に変換したい場合、Numberコンストラクタ
をメインで使用していきます。
// 数値以外の余計な文字列が含まれた文字列の場合 const age = "25歳"; console.log(parseInt(age)); // => 25 console.log(Number(age)); // => NaN
// 数値のみの文字列の場合 const age = "25"; console.log(parseInt(age)); // => 25 console.log(Number(age)); // => 25
数値を含まない文字列に使用した場合、parseInt()
もNumberコンストラクタ
もNaN
を返します。Rubyではそのような場合、0
になるので、仕様が異なります。
const age = "あ"; console.log(parseInt(age)); // => NaN console.log(Number(age)); // => NaN
整数を文字列に変換する
Ruby
Rubyで整数を文字列に変換する場合、Integer#to_s
を使用します。
age = 25 p age.to_s # => "25"
もし、ageがnil
の場合、to_s
を実行するとNilClass#to_s
が呼ばれます。NilClass
はnil のクラスです。 nil は NilClass クラスの唯一のインスタンスです。NilClass#to_s
は空文字列を返します。
age = nil p age.to_s # => ""
Rubyではto_s
以外に、式展開で整数を文字列に変換できます。
age = 25 p "#{age}" # => "25"
TS
TSで整数を文字列に変換する場合、テンプレートリテラルを使うのがシンプルかつ覚えやすいです。
const age = 25; console.log(`${age}`); // => "25"
参考記事
String#to_i (Ruby 3.2 リファレンスマニュアル)
【Ruby】 to_iメソッドの使い方を理解しよう | Pikawaka
Number() コンストラクター - JavaScript | MDN
JavaScriptのparseInt()とNumber()の違い - Qiita
【TS】【Ruby】配列に要素を追加する方法をざっくりまとめてみた
目次
背景
配列に要素を追加する方法を忘れそうになるので、ブログにまとめます。
配列に要素を追加する
Ruby
RubyではArray#push
メソッドを使用します。このpushメソッドは破壊的メソッドなので、元のオブジェクトを変更します。
nums = [1, 2] nums.push(5) p nums # => [1, 2, 5]
もし非破壊的に要素を追加したい場合、スプラット演算子を使用します。
nums = [1, 2] new_nums = [*nums, 5] p new_nums # => [1, 2, 5] p nums # => [1, 2]
TS
TSではArray.prototype.push()
メソッドを使用します。このpushメソッドは破壊的メソッドなので、元のオブジェクトを変更します。
const nums = [1, 2]; nums.push(5); console.log(nums); // => [1, 2, 5]
もし非破壊的に要素を追加したい場合、スプレッド構文を使用します。
const nums = [1, 2]; const newNums = [...nums, 5]; console.log(newNums); // => [1, 2, 5] console.log(nums); // => [1, 2]
参考記事
【TS】【Ruby】日付をyyyy-mm-ddに変換する方法をざっくりまとめてみた
目次
背景
仕事で日付をyyyy-mm-ddに変換するのをやったときにパッとできなかったので、忘れないように記事にしようと思います。
日付をyyyy-mm-ddに変換する方法
Ruby
まずRubyで日付を生成するには、Dateクラスを使用します。Date.todayで今日の日付を持つDateクラスのインスタンスを生成できます。
require 'date' today = Date.today p today # => #<Date: 2022-12-04 ((2459918j,0s,0n),+0s,2299161j)>
この日付をyyyy-mm-ddに変換したい場合、Date#strftimeを使用します。 strftimeには文字列でフォーマットを指定します。フォーマットについて知りたい場合、Date#strftimeには詳しい説明が書いていなかったので、Time#strftimeを見た方が良いです。
require 'date' today = Date.today.strftime("%Y-%m-%d") p today # => "2022-12-04"
TS
まずTSで日付を生成するには、Dateオブジェクトを使ってインスタンスを生成します。Dateをnewするときにコンストラクタ引数を何も渡さない場合、作成されるインスタンスは現在の時刻を表すものになります。
const today = new Date(); console.log(today); // => Date: "2022-12-04T23:33:34.966Z"
getMonth()メソッドは、月を0から11の整数で取得します。そのため、+ 1しています。
// YYYY/MM/DD形式の文字列に変換する関数 const formatDate = (date: Date) => { const yyyy = String(date.getFullYear()); // Stringの`padStart`メソッド(ES2017)で2桁になるように0埋めする // 既に長さを満たしている場合、0埋めされない const mm = String(date.getMonth() + 1).padStart(2, "0"); const dd = String(date.getDate()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}`; } const today = new Date(); console.log(formatDate(today)); // => "2022-12-05"
参考記事
class Date (Ruby 3.1 リファレンスマニュアル)
Date#strftime (Ruby 3.1 リファレンスマニュアル)
Time#strftime (Ruby 3.1 リファレンスマニュアル)