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
参考文献: