一.出発点
React の既存のコンポーネントモデルの下では、多くの解決が難しい問題が存在します:
-
コンポーネント間で状態ロジックを再利用するのが難しい
-
コンポーネントの複雑度が高く理解が難しい
-
Class の多くの弊害
-
……
そして Hooks は、局面を打破する使命を担っています
コンポーネント間ロジックの再利用
コンポーネント間ロジックの再利用は常に問題でした。Render Props、Higher-Order Components などの一般的な套路パターンはすべて、横断的関心事(Cross-cutting concern)を分離し、以下のようなものを再利用するためのものです:
-
ログ
-
キャッシュ/同期/永続化
-
データ検証
-
エラーキャプチャ/例外処理
のロジックで、目的は横断的関心事とコアビジネスロジックを分離し、ビジネスロジックに集中できるようにすることです
P.S. アスペクト、関心事などの AOP 概念に関する詳細情報は、AOP(Aspect-Oriented Programming) を参照
しかし、HOC と Render Props はコンポーネント形式で横断的関心事を分離できますが、いくつかの新しい問題ももたらします:
-
拡張性の制限
-
Ref 受け渡し問題
-
Wrapper Hell
これらの問題が発生する根本的な理由は��
細粒度コードの再利用は、コンポーネントの再利用と捆绑されるべきではありません
そして、これまでシンプルで直接的なコンポーネント行動拡張方式が不足していました:
React doesn't offer a way to "attach" reusable behavior to a component (for example, connecting it to a store).
Hooks を提案した主な目的はこの問題を解決することです:
React needs a better primitive for sharing stateful logic.
P.S. コンポーネント間ロジックの再利用方式に関する詳細情報は、[React コンポーネント間ロジックの再利用](/articles/react コンポーネント間ロジックの再利用/) を参照
コンポーネントの複雑度問題
ご存知の通り、React コンポーネントはますます複雑になっています:
Provider, Consumer, Higher-order Component, Render Props
// with Redux
Action, Reducer, Container
// with Reselect
Selector
// with xxx ...
などの多くの抽象層は状態ロジックの組織と再利用問題を緩和しましたが、それに伴ってコンポーネントの再利用コストが高くなり、もはやコンポーネントを導入するだけで完全なビジネス機能を獲得できるものではなくなりました
確かに、これらの細分化された抽象層はコードの責務をより明確にできますが、状態ロジックも分散され、再利用が難しくなります。そのため、コンポーネントの再利用度はほとんどが View Component (View + UI State Logic) レベルに留まり、Business Component (View + Business State Logic) をさらに再利用することはできません。これらのビジネス状態ロジック(データリクエスト、他のデータソースの購読、タイマーなど)をすべて単一のコンポーネントに集約しない限り(例えば mobxjs/mobx に変更して状態を管理)。即便如此、コンポーネントに混在する副作用や、ライフサイクルメソッドに混在する無関係なロジックを避けることはできません。例えば componentDidMount にはデータリクエスト、イベントリスニングなどが含まれています……我们发现,真に関連性のあるコードはライフサイクルによって分割され、完全に無関係なコードが最終的に 1 つのメソッドにまとめられています:
Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method.
コンポーネントのライフサイクルに従ってロジックを分割すると、コンポーネントの体积が急速に膨張し、このような巨大なコンポーネントを一連の小さなコンポーネントに分割するのは容易ではありません。状態ロジックが至る所にあり、テストも難しいからです
したがって、より合理的で、より再利用しやすい状態ロジックの組織方式 が必要です。真に関連性のあるコードを一緒に集め、ライフサイクルメソッドによって強制的に分割されないようにします
P.S. MobX に関する詳細情報は、MobX を参照
Class の弊害
Class はオブジェクトの型として、OOP の非常に重要な部分です。しかし、Class を使用して(ビューとロジックが結合した)コンポーネントを定義するのはそれほど理想的ではありません:
-
コードの組織/再利用が難しくなる
-
より高い学習コストをもたらす
-
コンパイル最適化を阻害する
コードの組織/再利用の面では、Class のライフサイクルメソッドが典型的な例です。1 つのコンポーネントには特定のライフサイクルメソッドが 1 つしか存在できないため、いくつかの無関係なロジックを一緒に配置する必要があり、このようなテンプレート式の分割 は真に関連性のあるコードを分割し、コードの再利用に不利です
学習コストは、主に 2 点に現れます:
-
JavaScript 中の
thisを理解する(他の言語とは異なる)必要があり、bind(this)を覚えておく必要がある -
関数型コンポーネントと Class コンポーネントの違い、およびそれぞれの適用シーンを理解する
コンパイル最適化の面では、Class によりいくつかのツールの最適化効果が大幅に低下します。例えば:
-
コンポーネントの事前コンパイル(ahead-of-time compilation)効果が理想的でない(React チームはすでにこの方面で いくつかの試み を行い、Class コンポーネントがコンパイル最適化に不利であることを発見)
-
Class はコード圧縮に不利
-
正しいホットリロード(hot reloading)が難しい
P.S. コンポーネントの事前コンパイルは [GCC の高级模式](/articles/react 背後のツール化体系/#articleHeader11) に類似し、defaultProps 定数などをインライン最適化し、不要なコードを削除
したがって、コンパイル最適化に優しい API を提供したい:
We want to present an API that makes it more likely for code to stay on the optimizable path.
そのため Class を捨て、関数を受け入れる:
Hooks let you use more of React's features without classes.
P.S. 関数型プログラミングに転向し、FP 概念を全面的に導入するのではなく、命令型プログラミングへの「脱出ハッチ」を提供するもので、関数型プログラミング、反応型プログラミングなどの技術を習得する必要はありません:
Hooks provide access to imperative escape hatches and don't require you to learn complex functional or reactive programming techniques.
二.目標
以上の种种の問題を解決するために、Hooks が誕生しました。目標は:
-
シンプルで直接的なコードの再利用方式を提供
-
より合理的なコードの組織方式を提供
-
Class の代替方案を提供
一方でコードの組織、再利用の問題を解決し、他方で、新しいコンポーネント定義方式も React の将来のビジョンの一部です:
Hooks represent our vision for the future of React.
では、Hooks は一体何なのでしょうか?
三.位置付け
Hooks は、関数型コンポーネントが React の State やライフサイクルなどの機能にアクセスできるようにする関数 です:
Hooks are functions that let you "hook into" React state and lifecycle features from function components.
一方で Hooks を通じてコードをより合理的に分割/組織し、再利用問題を解決し、他方で Hooks を通じて関数型コンポーネントを強化し、Class コンポーネントと同じ表現力を持たせ、代替オプションとなり、最終的に取って代わります
四.作用
Hooks は主にコードの組織、ロジックの再利用方面的问题を解決します。例えば:
-
ライフサイクルによって分割された関連ロジックを組織する。データソースの購読/購読解除、イベントリスナーの登録/解除など
-
ライフサイクル中に散在する重複ロジックをコンポーネント間で再利用し、同時に HOC や Render Props などのコンポーネント合成に基づく再利用モードがもたらす��ンポーネントのネスト問題(Wrapper Hell)を解決
さらに、React 自身にとって、Hooks は大規模最適化の障壁も解決します。例えばコンポーネントのインラインコンパイルの難題など
コードの組織
Hooks 方案の下では、最大の違いは、コンポーネントをライフサイクルメソッドに従って強制的に分割するのではなく、コードブロックの内在的関連性に基づいていくつかの小さな関数に分割できる ことです:
Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
例えば:
// カスタム Hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// 外部データソースの登録/解除
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
外部データソースの登録/解除のコードが密接に联系在一起び、コンポーネントのライフサイクル)の違いを気にする必要がなくなります
ロジックの再利用
同時に、上記の例のように、これらの状態ロジックと副作用を簡単に Hooks に抽出し、Custom Hook に組成でき、状態ロジックの再利用問題を綺麗に解決します:
With Hooks, you can extract stateful logic from a component so it can be tested independently and reused.
例えば:
// View コンポーネント 1
function FriendStatus(props) {
// カスタム Hook を使用
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// View コンポーネント 2
function FriendListItem(props) {
// カスタム Hook を使用
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
另一方面、このような再利用方式は「無傷」で、コンポーネントツリーの階層構造を調整する必要がなく、導入してすぐに使用 できます:
Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.
五.まとめ
単に形式から見ると、Hooks は関数型コンポーネントの強化であり、Class コンポーネントと並ぶことができ、さらに(期待としては)取って代わることができます。実質的な意義は、さらに多くの関数型思想をフロントエンド領域に導入すること です。例えば Effect、Monad など。v = f(d) の UI 層関数型思路を提案した後の、この道でのさらなる探求と言えます
(React 16 Roadmap から引用)
ある意味では、このような思想風暴は Concurrent Mode などの核心特性よりも興奮させるものです
コメントはまだありません