なぜ
React Hooksを紹介するにあたって、理解すべきことが2つあります:
React Hooksはなぜ存在するのか?
どのような問題を解決するのか
Reactが過去にどのように書かれていたかを理解することから始めましょう。
React.createClass
createClassは、コンポーネントを作成するシンプルで効率的な方法です。 createClassが最初に使われた理由は、当時JavaScriptにはクラスシステムがなかったからです。
React.Component
React v0.13.0ではReact.Componentが導入され、JavaScriptのクラスでコンポーネントを作成できるようになりました。
では、これを使うとどのような問題があるのでしょうか。
コンストラクタ
クラスを使ってコンポーネントを作成する場合、コンポーネントの状態を初期化するためにコンストラクタのメソッドが必要になりますが、React.Componentの継承により、すべてのコンストラクタでsuperを呼び出す必要があります。
constructor (props) {
super(props) //
...
}
自動バインディング
constructor (props) {
...
this.updateRepos = this.updateRepos.bind(this) //
}
第一に、これらは単純な問題であり、super(props)と手動バインドの呼び出しは面倒ですが、全体としては大した問題ではありません。第二に、これらはReactの問題とはみなされません。JavaScriptのクラスはそのように設計されているからです。しかし、プログラマーとしては、最も単純な問題でさえ1日に20回以上繰り返すのは迷惑なことです。
クラスフィールド
クラスフィールドを使用すると、コンストラクタを使用せずにインスタンスプロパティをコンポーネントクラスに直接追加して割り当てることができます。これにより、状態を直接初期化し、アロー関数によるバインドの問題を解決することができます。
class ReposGrid extends React.Component {
state = {
repos: [],
loading: true
}
componentDidMount () {
this.updateRepos(this.props.id)
}
componentDidUpdate (prevProps) {
if (prevProps.id !== this.props.id) {
this.updateRepos(this.props.id)
}
}
updateRepos = (id) => {
this.setState({ loading: true })
...
}
...
}
これで十分でしょうか?まだまだです。
Reactの核となるアイデアは、アプリの複雑さを個々のコンポーネントに分解し、コンポーネントを組み合わせることで管理できるようにすることです。このようなコンポーネントモデルは、Reactをエレガントにするでしょう。しかし、現在の問題はコンポーネント・モデルではなく、モデルの実装方法にあります。
ロジックの重複
歴史的な理由から、コンポーネントの構築方法はコンポーネントのライフサイクルに直接関係しています。このライフサイクルの分割により、コンポーネント全体にロジックが分散することになります。下の例ではっきりわかるように、reposとprops.idを同期させるという同じことをするために3つのメソッドが必要です。
componentDidMount () {
this.updateRepos(this.props.id)
}
componentDidUpdate (prevProps) {
if (prevProps.id !== this.props.id) {
this.updateRepos(this.props.id)
}
}
updateRepos = (id) => {
this.setState({ loading: true })
fetchRepos(id)
.then((repos) => this.setState({
repos,
loading: false
}))
}
これを解決するには、Reactコンポーネントの副作用を処理する新しいパラダイムが必要です。
視覚的でないロジックの共有
Reactの組織形態について考えるとき、UIの組織視点で考えている確率が高いでしょう。ReactはUIが得意なので、これは普通のことです。
ビュー = fn(ステート)現実には、UIレイヤーだけを行うアプリを作るだけでは不十分です。ビジュアル以外のロジックを再利用する必要性は、一般的な要件です。しかし、ReactはUIをコンポーネントに結合するため、非ビジュアルの再利用が難しくなります。歴史的に、Reactはこの問題をあまり解決してきませんでした。レポの状態も必要とする別のコンポーネントを作成する必要がある場合は、この例と組み合わせてください。現在、論理的な処理と状態はReposGridコンポーネントにあります。これを再利用するには?最も簡単な方法は、ネットワーク要求とデータ処理のロジックをすべてコピーして、新しいコンポーネントに貼り付けることです。もちろん、これは一時的な解決策に過ぎません。より良い解決策は、すべてのパブリックロジックをカプセル化するHOCを作成し、それらを必要とするコンポーネントにローディングとReposをpropsとして渡すことでしょう。
view = fn(state)
そうすれば、レポが必要なコンポーネントをwithRepos HOCでラップすることができます。
function withRepos (Component) {
return class WithRepos extends React.Component {
state = {
repos: [],
loading: true
}
componentDidMount () {
this.updateRepos(this.props.id)
}
componentDidUpdate (prevProps) {
if (prevProps.id !== this.props.id) {
this.updateRepos(this.props.id)
}
}
updateRepos = (id) => {
this.setState({ loading: true })
fetchRepos(id)
.then((repos) => this.setState({
repos,
loading: false
}))
}
render () {
return (
<Component
{...this.props}
{...this.state}
/>
)
}
}
}
この解決策は機能し、目に見えないロジックを共有するための推奨される解決策です。しかし、いくつかの欠点があります。
まず、HOCに慣れていない場合、ロジックを追うのが少し難しいと感じるでしょう。例えばwithRepos HOCは、最終的なレンダリングを担当するコンポーネントを入力として受け取り、ロジックを含む新しいコンポーネントを返す関数です。このプロセスは複雑で、理解するのは簡単ではありません。
第二に、より多くのHOCが必要な場合、すぐにコードがうまく制御できなくなることが想像できます。
// ReposGrid.js
function ReposGrid ({ loading, repos }) {
...
}
export default withRepos(ReposGrid)
// Profile.js
function Profile ({ loading, repos }) {
...
}
export default withRepos(Profile)
上記よりもさらに悪いのは最終的なレンダリングで、HOCはラップされたコンポーネントの再構築を強制します。これは最終的に "パッケージング地獄 "につながり、維持するのは困難です。
export default withHover(
withTheme(
withAuth(
withRepos(Profile)
)
)
)
現在の状態
現在の状態は
- React
- Reactのコンポーネントはクラスで書かれています。
- super(props)を呼び出すのは面倒
- 誰もthisがどのようにバインドされているのか知りません。
- あなたはthisがどのようにバインドされているか知っていますが、他の人にとっては不必要な負担です。
- コンポーネントをライフサイクルで整理することで、関連するロジックをコンポーネントに分散させやすくなります。
- Reactには、視覚的でないロジックを共有するための良い原始的なソリューションがありません。





