目次
- 目次
- 概要
- 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オブジェクトのインスタンスを作成する