ReactのPresentationalコンポーネントに渡す関数は on から始めたい
主張
React のコンポーネントの実装は、見た目の振る舞いを定義するコンポーネント(Presentationalコンポーネント)と、ビジネスロジックを実装するコンポーネント(Containerコンポーネント)に分けて実装することが多い。[1]
一方で、複雑なアプリケーションを実装していくうちに、ビジネスロジックがPresentationalコンポーネントに入ってしまうことが往々にしてある。
これを防ぐために、React の Presentational Component に渡す関数は on から始めたい。
何を防ぎたいか?
簡単な例として、以下のようなログインフォームを考えてみる。UserLoginForm は、ログインフォームの見た目を扱うコンポーネントとする。
type Props = {
name: string;
password: string;
changeName: (name: string) => void;
changePassword: (password: string) => void;
login: () => void;
}
const UserLoginForm: React.FC<Props> = (props) => {
return (
<form onSubmit={(e) => {
e.preventDefault();
if(isValid(name, password)) props.login();
}}>
<input
type="name"
value={name}
onChange={(e) => changeName(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => changePassword(e.target.value)}
/>
<button>ログイン</button>
</form>
)
}
この例だと、if(isValid(name, password)) props.login()
の部分で、入力結果が valid かどうかを判定するビジネスロジックがPresentationalコンポーネントに入っている。
こういった副作用がないロジックは往々にして入りがちである。理想的にはこの処理はより外側にあるContainerコンポーネントに委譲させるべきである。
なぜ防ぎたいか?
一番の目的は、単一責任の原則を守るため[2]である。見た目だけの責務だけに閉じることで、コンポーネントの責務が少なくなる。
また、この原則を守ることで、ロジックに修正があった場合の変更箇所が集約されることや、コンポーネントの詳細について親コンポーネントが詳細を知る必要が少なくなるなど、設計上のメリットがある。
click と onClick の違い
on を付ける場合と付けない場合の違いはなんだろう?
以下の記事では、jQuery の click と onclick の違いについて、click は method で、onclick は event であると記述している。
click is the method, onclick is an event.
以下の記事では、prefix に on を付ける場合の意味について議論している。この議論を読むと、on は、「いつ?」という意味を表していることや、直接呼び出されるものではなく、event handler として設定されるもので、callback として使われることが多いということが書かれている。
In this context, on <something> means when <something>.
Prefix "on" is most often used to indicate that method is intended to be used as a callback, i.e. not called directly, but set as a handler for some event.
以上より、 on を prefix につけることで、処理の内容の中身ではなく、その callback を発火させるタイミングとしての意味合いが強くなると言えそう。
Presentationalコンポーネントは、関数の処理自体には関心を持たない。そのため、on をつけるのが適切なケースだと考えている。
実は同様の主張は他でもされていて、以下では、event handler に on を、 その処理に handle prefix を付けるべきだと主張している。
<button onClick={handleClick} />
まとめ
- Reactのコンポーネントは大きくPresentationalとContainerコンポーネントに分けられる
- onがつくことで、処理の内容の中身ではなく発火させるタイミングの意味合いが強くなる
- Presentationalコンポーネントにロジックを入れないようにpropsにはonを追加したい
以上を踏まえて、UserLoginFormの実装を書き直してみる。
type Props = {
name: string;
password: string;
onNameChange: (name: string) => void;
onPasswordChange: (password: string) => void;
onFormSubmit: () => void;
}
const UserLoginForm: React.FC<Props> = (props) => {
return (
<form onSubmit={(e) => {
e.preventDefault();
props.onFormSubmit();
}}>
<input
type="name"
value={name}
onChange={(e) => onNameChange(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => onPasswordChange(e.target.value)}
/>
<button>ログイン</button>
</form>
)
}
Discussion