React Hooksを備えた機能コンポーネント。 なぜ彼らは良いですか?

比較的最近、React.js 16.8がリリースされ、フックが使用可能になりました。 フックの概念により、Reactのすべての機能を使用して本格的な機能コンポーネントを記述でき、クラスを使用するよりも多くの方法でこれをより便利に行うことができます。


多くの人が批判のあるフックの出現を認識しており、この記事では、フック付きの機能コンポーネントがもたらす重要な利点と、それらに切り替えるべき理由についてお話したいと思います。


フックの使用の詳細については意図的に掘り下げません。 これは、この記事の例を理解する上でそれほど重要ではありません; Reactの一般的な理解で十分です。 このトピックを正確に読みたい場合は、フックに関する情報がドキュメントにあります。このトピックが興味深い場合は、いつ、何を、どのようにフックを正しく使用するかについて詳しく説明します。


フックはコードの再利用を容易にします


単純な形状をレンダリングするコンポーネントを想像してみましょう。 単にいくつかの入力を出力し、それらを編集できるようにするもの。


このようなもの、非常に単純化された場合、このコンポーネントはクラスのように見えます:


class Form extends React.Component { state = { //   fields: {}, }; render() { return ( <form> {/*    */} </form> ); }; } 

ここで、フィールドの値が変更されたときに自動的に保存したいことを想像してください。 shallowEqualdebounceなどの追加関数の宣言を省略することをお勧めします。


 class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { //   fields: {}, // ,       draft: { isSaving: false, lastSaved: null, }, }; saveToDraft = (data) => { if (this.state.isSaving) { return; } this.setState({ isSaving: true, }); makeSomeAPICall().then(() => { this.setState({ isSaving: false, lastSaved: new Date(), }) }); } componentDidUpdate(prevProps, prevState) { if (!shallowEqual(prevState.fields, this.state.fields)) { this.saveToDraft(this.state.fields); } } render() { return ( <form> {/*    ,     */} {/*    */} </form> ); }; } 

同じ例ですが、フックがあります:


 const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

ご覧のとおり、違いはまだそれほど大きくありません。 useStateフックに変更し、 useStateではなくuseEffectフックを使用してコンポーネントをレンダリングした後、ドラフトに保存します。


ここで示したい違い(他にもあります。それらについては以下で説明します):このコードを取り出して、別の場所で使用できます。


 //  useDraft       const useDraft = (fields) => { const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return [draftIsSaving, draftLastSaved]; } const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

これで、他のコンポーネントでuseDraftuseDraftフックuseDraft使用できます! これはもちろん非常に単純化された例ですが、同じタイプの機能を再利用することは非常に便利な機能です。


フックを使用すると、より直感的なコードを作成できます。


たとえば、現在のチャットウィンドウ、可能な受信者のリスト、およびメッセージ送信フォームを表示するコンポーネント(これまでのところクラスの形式)を想像してください。 このようなもの:


 class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${this.state.currentChat} `); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; } 

この例は非常に条件付きですが、デモには非常に適しています。 次のユーザーアクションを想像してください。



しかし、メッセージはチャット1に送信されましたか? これは、クラスメソッドが、送信時の値では機能せず、リクエストが完了した時点の値では機能するために発生しました。 このような単純なケースではこれは問題になりませんが、そもそもこのような動作を修正するには追加の注意と追加の処理が必要になり、次にバグの原因になる可能性があります。


機能コンポーネントの場合、動作は異なります。


 const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${currentChat} `); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; } 

同じユーザーアクションを想像してください。



それで、何が変わったのでしょうか? 変更されたのは、 currentChatが異なる各レンダーに対して、新しいメソッドを作成していることです。 これにより、将来何かが変わるかどうかをまったく考えずに済みます。 現在の作業に取り組んでいます各レンダーコンポーネントは、それ自体に関連するすべてを閉じます


フックはライフサイクルから私たちを救います


この項目は前の項目と重複しています。 Reactは、インターフェースを宣言的に記述するためのライブラリです。 宣言は、コンポーネントの記述とサポートを大幅に促進し、Reactを使用しなかった場合に何をする必要があるかについて考えることを減らします。


それにもかかわらず、クラスを使用する場合、コンポーネントのライフサイクルに直面します。 さらに深くしないと、次のようになります。



便利そうに思えますが、私はそれが習慣のためだけに便利であると確信しています。 このアプローチは、Reactとは異なります。


代わりに、フック付きの機能コンポーネントを使用すると、ライフサイクルではなく同期について考えてコンポーネントを作成できます。 その結果が外部パラメータと内部状態に応じてインターフェイスの状態を一意に反映するように関数を記述します。


useEffect 、多くがcomponentDidMountcomponentDidUpdate直接の置換として認識しcomponentDidMount 、実際には別のものを対象としています。 それを使用する際に、「これをレンダリングした後、これらのエフェクトを実行してください」という反応を伝えます。


useEffectに関する大きな記事のクリックカウンターでコンポーネントがどのように機能するかの良い例を次に示します。



もっと宣言的ではありませんか?


まとめ


React Hookにより、いくつかの問題を取り除き、コンポーネントの理解とコーディングを容易にすることができます。 あなたが適用するメンタルモデルを変更するだけです。 機能コンポーネントは、本質的にパラメーターのインターフェース機能です。 彼らはすべてのことをいつでもあるべき姿で説明し、変化への対応方法を考えないように助けます。


はい、時にはそれらを正しく使用する方法を学ぶ必要がありますが、同じ方法で、クラスの形でコンポーネントをすぐに使用する方法を学びませんでした。



Source: https://habr.com/ru/post/J443488/


All Articles