React 16.8 は Hooks
の機能を正式にリリースしました。この間に、いくつかの側面から Hooks
を試してみたので、参考にしたいくつかの記事を基に Hooks
についてお話しします。
Hooks
を使うことで、クラスを使わずにReact
のstate
やその他の機能を利用できます。また、自分自身のHooks
をカスタマイズして、コンポーネント間で再利用可能な状態ロジックを共有することもできます。
起源#
現在、コンポーネントとトップダウンのデータフローは、大規模な UI を小さく、独立して再利用可能な部分に整理するのに役立ちます。しかし、多くの場合、私たちのロジックは状態を持っているため、複雑なコンポーネントをさらに分解することができず、関数や別のコンポーネントを分割することもできません。これらの状況には、アニメーション、フォームコントロール、外部データソースとの接続 などが非常に一般的です。
現在、React
アプリケーションでは、これらのロジックを再利用するための多くの方法があります。例えば、単純な関数を書いて呼び出すか、コンポーネント(関数またはクラスの形式)を通じて行いますが、非可視化のロジックに関してはそれほど便利ではありません。そのため、render props
や高階コンポーネントのような複雑なパターンが提案されました。
もし React
にロジックを再利用するための一般的な方法があれば、もっと簡単になるでしょうか?
コードの再利用に関して、関数は完璧なメカニズムのように思えますが、関数はローカルな React
の状態を含むことができません。私たちは、クラスコンポーネントからいくつかの動作を単純に抽出することはできません。例えば、ウィンドウのサイズに基づいて状態を更新したり、時間の経過とともに値を変化させたりすることです。通常、私たちはこの部分のコードをリファクタリングする必要があるか、オブザーバーのような抽象的なパターンを採用する必要があります。これらはすべて React
のシンプルさを損ないます。
これらの考えに基づいて、React
開発チームは Hooks
を提案しました。これにより、関数を通じて React
の機能(例えば状態)を利用できるようになり、React
公式もいくつかの組み込みの Hooks
を提供しています:状態、ライフサイクル、コンテキスト。Hooks
は通常の JavaScript
関数であるため、私たちもカスタム Hooks
を作成して使用できます。
デモ#
ウィンドウのサイズの変更に応じて React
の状態を更新する例を見てみましょう。
import { useState, useEffect } from 'react'
function useWindowWidth () {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
上記のコードでは、React
に組み込まれている最も一般的な 2 つの Hooks
を使用しています:useState
と useEffect
:
React
の基本的なHooks
にはもう一つ useContext があります。
同時に、私たちは組み込みの Hooks
に基づいたカスタム Hooks
も持っています:useWindowWidth
。
useWindowWidth
が何をしているのか見てみましょう:ウィンドウの初期サイズを渡し、状態値 width
と width
を更新するための setWidth
を返します。useEffect
内部では、setWidth
を使ってウィンドウのサイズを設定する handleResize
関数を定義し、ウィンドウのリサイズイベントをリッスンしてウィンドウのサイズを更新します。最終的に、リスナーを解除する関数を返します。
これがウィンドウのサイズに応じてレイアウトを調整できる Hooks
です。
もしクラスコンポーネントの方法で実装したらどうなるでしょうか?
class setWidth extends React.Component {
constructor (props) {
super(props)
this.state = {
width: window.innerWidth
}
}
handleResize = () => this.setState({ width: window.innerWidth })
componentDidMount () {
window.addEventListener('resize', handleResize)
}
componentWillUnmount () {
window.removeEventListener('resize', handleResize)
}
render () {
return <SomeUI />
}
}
比較すると、クラスコンポーネントの書き方では、state
を設定し、煩雑なライフサイクルを組み合わせ、UI をレンダリングする必要があります(もちろん null
でも構いません)。さらに大きな問題は、このコンポーネントを再利用する必要があり、<SomeUI />
が同じであってはならない場合、高階コンポーネントの書き方を採用することが多く、これが抽象的なパターンを引き起こします。したがって、比較すると、Hooks
は開発者にとって学習コストが小さく、React
のシンプルな理念により適合しています。
ここにクラスコンポーネントと
Hooks
のより直感的な比較を示す動画があります: https://twitter.com/i/status/1056960391543062528 。
同時に、カスタム Hooks
の魅力も感じることができます。Hooks
が正式に提案されたことで、今後ますます多くの Hooks
npm パッケージが開発者を助けるために登場するでしょう。
ここにさまざまなカスタム
Hooks
の実装を紹介するウェブサイトがあります: https://usehooks.com/ 。
深入#
React
開発者として、Hooks
が提案されたとき、私は非常に驚きました。React
の最初の目的は、より良い UI を構築することでした。これにより、2 つの疑問が生まれました:
Hooks
の登場は何を意味するのでしょうか?Hooks
はどのように実現されているのでしょうか?
React Hooks に関するいくつかの考察 の中に次のような言葉があります:「状態を持つコンポーネントはレンダリングされず、レンダリングされるコンポーネントは状態を持たない」。React
チームが推進したい正しい理念は「状態と UI を分ける」ことです。そして、これは実際に React Hooks
の特性と一致します。Hooks
を使用する際の 2 つのルール を見てみましょう:
- 関数のトップでのみ
Hooks
を呼び出すこと。 React
関数内でのみHooks
を呼び出すこと。
Hooks
は関数のトップに集中して書く必要があります。これは、無状態の UI コンポーネントを書く習慣を容易に身につけることができ、「状態と UI を分ける」という理念を実践しやすくします。個人的には、これが React
チームが Hooks
を 状態共有の問題 に対する長年の最も完璧な解決策と見なす理由の一つだと思います。
では、なぜ Hooks
は関数のトップで呼び出す必要があるのでしょうか?それは、条件判断で Hooks
を包むことを防ぐためです。条件判断で包むことができない理由は、Hooks
の実装の問題に関係しています。簡単に説明すると、Hooks
は Proxy
や getters
を使用して実現されているのではなく、配列のような実装方式を使用しています。そのため、Hooks
の正常な呼び出しはインデックスの順序に依存しています。毎回 useState
を呼び出すとインデックスが変わります。条件判断の中で Hooks
を使用すると、インデックスに影響を与え、Hooks
の呼び出しがエラーになる可能性があります。そのため、公式も Hooks
内部で条件判断を行うことがより正しい方法であると推奨しています。
詳細な理由については 公式文書 を参照してください。
Hooks
の原理については React hooks: not magic, just arrays を参照してください。
Hooks
システムについてさらに深く理解したい場合は、Under the hood of React’s hooks system を参照してください。
Hooks
にいくつかの制限があると感じる人もいるかもしれませんが、より正確な理解は、私たちが Hooks
の規約に従うべきであるということです。これは React
公式が初めて「規約優先」の理念を React
フレームワークに導入したものであり、制限があることで、より良い利便性が得られます。
Next.js
とUmi
には「規約ルーティング」の機能があり、ルーティング設定の複雑さが大幅に軽減されます。Hooks
はコードレベルの規約のようなもので、コードの複雑さを大幅に軽減します。これが将来の重要なトレンドであるかどうかを考えてみることができます。
さらなる考察#
Hooks
の設計は React
に限定されているわけではなく、現在では Vue
、Web Components
、さらにはネイティブの JavaScript
関数の実装(実験的な API 設計)も存在します。
あなたは次のような疑問を持つかもしれません:Vue
は Hooks
を必要とするのでしょうか?Vue
は確かに React
のクラスコンポーネントのような問題を抱えていませんが、ロジックを再利用する必要がある場合、Mixins
でも解決できます。しかし、本質的に見ると、Vue
は 状態共有 の問題を解決するために Hooks
を必要としています。Mixins
と比較して、Hooks
は Vue
に次の 2 つの主要な問題を解決するのに役立ちます:
- 状態の共有を実現すること。
- ロジックの流れをより明確に示すこと。
現在、Vue
チームは Vue
3.0 に Hooks
を統合する計画を立てていますが、React
の API から逸脱し、Vue
の思想に基づいて設計される予定です。そして、Mixins
の代替品となる可能性が高く、非常に探求する価値があります。
Vue の実験的な実装: https://github.com/yyx990803/vue-hooks
参考文献: