2024年に知っておくべき React デザインパターンのおすすめ
React の絶大な人気と実用性は否定できません。長い間、ほとんどのWebデザインは CSS、HTML、JavaScript で構築されていましたが、Reactはその使いやすさで、多くのデベロッパーから愛されています。Reactの最も魅力的な機能としては、再利用可能なコンポーネント、優れたデベロッパーツール、広範なエコシステムなどが挙げられます。
そして、DOMを直接操作するという従来のアプローチの代わりに、React は仮想 DOM コンセプトという形で有用な抽象化レベルを導入しました。
このライブラリは、技術大手である FacebookのReact開発者たちによって積極的に開発および保全されているため、他のフレームワークやライブラリよりもずっと優位に立っており、JavaScript コミュニティの大勢の貢献者も、React改善に貢献しています。
このような要因のおかげで、Reactは、常に新しいフレームワークが登場してフロントエンドデベロッパーの間で評価を競っているにも関わらず、デベロッパーの間で変わらず人気です。
React.js には数多くのデザインパターンが存在しますが、本記事では、Web アプリを構築する際にぜひ知っておきたい、Reactのおすすめパターンをいくつか見ていきます。
Git レポジトリ、Storybook、または npm からコンポーネントを使ってプロトタイプを構築しましょう。コンポーネントをデザインエディタにインポートすれば、デザイナーなしでも魅力的なレイアウトを作成できます。こちらから UXPin Merge へのアクセスをリクエストして、ぜひお試しください。
React のデザインパターンに従うべき理由
まず、デザインパターンが果たす役割を簡単に振り返ってみましょう。簡単に言うと、デザインパターンとは、ソフトウェア開発でよく起こる問題に対する再現可能なソリューションのことです。
それは与えられた要件に応じてプログラムの機能を構築できる基本テンプレートとして機能します。
ちなみに「デザインパターン」を「デザインシステム」と混同してはいけません。デザインシステムについては別の記事でお話ししています。
デザインパターンで、開発プロセスのスピードが上がるだけでなく、コードの解読や維持しやすくなります。
デザインパターンの一般的な例としては、Singletonパターンや Gang-of-Fourパターンなどが挙げられます。
ソフトウェア開発において、デザインパターンは以下のように2つの一般的な役割に関連付けられています。
- デベロッパーに共通のプラットフォームを提供する
- React のベスト プラクティスが確実に適用されるようにする
もっと詳しく見てみましょう。
役割1:デベロッパーに共通のプラットフォームを提供する
デザインパターンは、既知の問題に対する標準的な用語とソリューションを提供します。前述した Singleton パターンを例に挙げてみましょう。
このパターンは単一のオブジェクトを使うことを前提としており、このパターンを実装するデベロッパーは、特定のプログラムがシングルトンパターンに従っていることを他のデベロッパーに簡単に伝えることができ、そのデベロッパー達もその意味を理解できます。
役割2:React のベストプラクティスを確実に適用する
デザインパターンは、広範な調査とテストの結果として作成されたものであり、これでデベロッパーは開発環境に簡単に慣れることができるだけでなく、ベストプラクティスが遵守されるのを保証できるようになります。
その結果、エラーが減り、デバッグの時間が節約され、適切なデザインパターンが実装されていれば簡単に回避できたはずの問題の解明もできるようになります。
他のよくできたプログラミングライブラリのように、React はデザインパターンを広範に活用して、デベロッパーに強力なツールを提供しています。そしてデベロッパーは、React の哲学に適切に従うことで並外れたアプリケーションを作ることができます。
デザインパターンについてご理解していただけたところで、React.js で最も広く使われているデザインパターンを見ていきましょう。
関数コンポーネントとクラスコンポーネント
コンポーネントには、「関数コンポーネント」と「クラスコンポーネント」の2種類がありますが、両者の違いについては、以下のブログ記事で説明しています: 関数コンポーネント と クラスコンポーネントの違いとは?
クラスコンポーネント
クラスコンポーネントは、JavaScript のクラスに基づいて構築され、それでReact.Component クラスを拡張します。これは React コンポーネントの構築のための従来のアプローチであり、ステートとライフサイクルイベントを管理するための強固な構造を提供します。また、クラスコンポーネントは、ステートとライフサイクルの動作の正確な制御が非常に重要であるような複雑な場面で特に有利です。
クラスコンポーネントでは、React.Component を継承するクラスが定められます。このクラスには、状態を初期化するためのコンストラクタ、コンポーネントの存在のさまざまな段階を処理するためのライフサイクル メソッド、およびコンポーネントの UI を定めるためのレンダリングメソッドが含まれることがあります。この構造化されたアプローチは、内部状態とライフサイクルイベントの細心の注意を払った管理が必要な複雑なコンポーネントを処理する場合に有用です。
以下は、React のクラスコンポーネントの例です:
import React, { Component } from 'react'; class MyClassComponent extends Component { constructor(props) { super(props); this.state = { count: 0, }; // Binding 'this' context to the method this.incrementCount = this.incrementCount.bind(this); } incrementCount() { this.setState((prevState) => ({ count: prevState.count + 1, })); } render() { return ( <div> <h1>Count: {this.state.count}</h1> <button onClick={this.incrementCount}>Increment</button> </div> ); } } export default MyClassComponent;
関数コンポーネント
一方、関数コンポーネントは、典型的な JavaScript 関数に似ています。プロパティ(props)を引数として受け取り、レンダリングのために React 要素を返します。当初はクラス コンポーネントにある一部の機能が欠けていましたが、React Hooks の導入により関数コンポーネントが注目されるようになりました。
関数コンポーネントは、より単純な場面に適しており、それによってより関数型プログラミングのアプローチが促進されます。また、フックの出現により、関数コンポーネントは「ステート」のイベントと「ライフサイクル」のイベントを処理できるようになり、それで多くの場合、クラスコンポーネントが不要になりました。関数コンポーネントは、その簡潔な構文とわかりやすさにより、単純な UI 要素に最適です。
以下は、フックを使った React の関数コンポーネントの例です:
import React, { useState } from 'react'; const MyFunctionalComponent = () => { const [count, setCount] = useState(0); const incrementCount = () => { setCount(prevCount => prevCount + 1); }; return ( <div> <h1>Count: {count}</h1> <button onClick={incrementCount}>Increment</button> </div> ); }; export default MyFunctionalComponent;
複合パターン
React 開発者が連携して動作するコンポーネントが2つ以上ある場合、1つが親で、残りが子である可能性が高くなりますが、状態を共有してロジックを一緒に処理できることをご存知ですか?
それが複合コンポーネント React パターンの目的です。複合コンポーネント API で、コンポーネント間の関係が示され、コンポーネントが柔軟な方法で通信できるようになります。
詳しくは、LogRocket の React 複合コンポーネントの理解に関する記事をお読みください。
以下は、シンプル化されたAccordion component: を使った複合コンポーネントパターンの例です。
import React, { useState, createContext, useContext } from 'react'; // Create a context to share state between compound components const AccordionContext = createContext(); const Accordion = ({ children }) => { const [openIndex, setOpenIndex] = useState(null); const toggleIndex = (index) => { setOpenIndex(openIndex === index ? null : index); }; return ( <AccordionContext.Provider value={{ openIndex, toggleIndex }}> <div className="accordion">{children}</div> </AccordionContext.Provider> ); }; const AccordionItem = ({ children, index }) => { const { openIndex, toggleIndex } = useContext(AccordionContext); const isOpen = openIndex === index; return ( <div className={`accordion-item ${isOpen ? 'open' : ''}`}> <div className="accordion-header" onClick={() => toggleIndex(index)}> {children[0]} </div> {isOpen && <div className="accordion-body">{children[1]}</div>} </div> ); }; // Usage const App = () => { return ( <Accordion> <AccordionItem index={0}> <div>Header 1</div> <div>Content 1</div> </AccordionItem> <AccordionItem index={1}> <div>Header 2</div> <div>Content 2</div> </AccordionItem> <AccordionItem index={2}> <div>Header 3</div> <div>Content 3</div> </AccordionItem> </Accordion> ); }; export default App;
条件付きレンダリング
条件は、ソフトウェアデベロッパーにとって最も重要なツールです。
React のコンポーネントを書く過程で、状態に基づいて特定の JSX コードのレンダリングが必要になることがよくありますが、これは条件付きレンダリングによって実現されます。
条件付きレンダリングは、ニーズに基づいて個別のコンポーネントを作成し、アプリケーションで必要なコンポーネントのみをレンダリングできるため、非常に便利です。
たとえば、条件付きレンダリングを使って、ユーザーのログイン状態に基づいてユーザーにさまざまなメッセージを表示できます。そしてそのメッセージは isLoggedIn プロパティの値に従います。
レンダリングプロップ
よくある問題を解決するのにデザインパターンがどのように存在するかについてお話しました。React では、ロジックの繰り返しの問題を解決するのにレンダリングプロップを使うことができます。
React の公式ドキュメントによると、Render propsは「値が関数である prop を使って React コンポーネント間でコードを共有するための手法」と定義されています。
レンダリングプロップは、さまざまなコンポーネント間で同じ状態を共有できることから非常に便利であり、各コンポーネント内のロジックをハードコーディングする代わりに、関数 prop を使ってレンダリングする内容を決定できます。
Render props を利用するライブラリは、Formik、React Router、Downshift などがよく使われています。
以下は、Render Props パターンを示す例です:
import React, { Component } from 'react'; // Component using the Render Props pattern class Mouse extends Component { constructor(props) { super(props); this.state = { x: 0, y: 0 }; this.handleMouseMove = this.handleMouseMove.bind(this); } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ); } } // Usage of the Mouse component with render props const App = () => { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={({ x, y }) => ( <h2>The mouse position is ({x}, {y})</h2> )}/> </div> ); }; export default App;
制御コンポーネント
Web フォームは多数のアプリケーションでのよくある要件であり、制御されたコンポーネントは、フォームの状態を処理するための React の答えです。
制御コンポーネントは、props を介して状態を取得し、onChange などのコールバックを使って変更を通知できます。
親コンポーネントはコールバックを処理し、独自の状態を管理することでそれを制御できます。その間、新しい値は制御対象のコンポーネントにプロパティとして渡されます。
デフォルトでは、React フォームは制御コンポーネントと非制御コンポーネントの両方に対応していますが、制御コンポーネントの使用を強くお勧めします。
次のコード スニペットは、制御コンポーネントを示しています。
<input type = “text” value = {value} onChange = {handleChange} />
React フックパターン
フックは React に比較的新しく追加されたもので、React 16.8 で導入されました。
このような関数により、デベロッパーはクラスなしで React を使うことができ、エフェクトフック(useEffect)や State フックなど、さまざまなフックが事前構築されています。
利用可能なフックの完全なリストについては、フック API のリファレンスを参照してください。
また、React の事前構築済みのフックとは別に、独自のフックを作成することもでき、これにより、コンポーネントロジックを抽出し、再利用可能な関数を作成できます。
フックは Reactの追加機能であり、デベロッパーコミュニティではこの新しい追加が非常に熱心に高く評価されました。
ただし、引数がオブジェクト、配列、または関数の場合、フックの操作が少し難しい場合があることを覚えておかないといけません。これはやや混乱する可能性がありますからね。
一方、カスタムフックは使いやすく、デベロッパーにも計り知れないメリットをもたらします。
以下は、フックを使った React の関数コンポーネントの例です:
import React, { useState, useEffect } from 'react'; const Counter = () => { // useState hook to manage the counter state const [count, setCount] = useState(0); // useEffect hook to perform a side effect: updating the document title useEffect(() => { document.title = `Count: ${count}`; }, [count]); // Only re-run the effect if count changes // Function to handle incrementing the count const increment = () => { setCount(prevCount => prevCount + 1); }; // Function to handle decrementing the count const decrement = () => { setCount(prevCount => prevCount - 1); }; return ( <div> <h1>Counter: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); }; export default Counter;
高階コンポーネントパターン
より高度な React パターンに関しては、「HOC」という高階コンポーネントパターンがあり、これは、React デベロッパーがアプリケーション内でロジックを再利用したいときに適用されます。
HOC はコンポーネントを引数として受け取り、それを返すときに、コンポーネントにデータと機能を追加します。
たとえば、Redux で React を使う場合、connect関数を介してコンポーネントを渡すと、Reduxストアからデータが挿入されます。そして取得した値は Props として渡されます。
HOC はコアの React API の一部ではなく、JavaScript 関数です。ですが、 継承よりも構成を重視する React 関数コンポーネントの性質と一致しています。
以下は React の HOC パターンの例です:
import React from 'react'; // Higher-Order Component const withLogging = (WrappedComponent) => { class WithLogging extends React.Component { componentDidMount() { console.log(`Component ${WrappedComponent.name} is mounted.`); } componentWillUnmount() { console.log(`Component ${WrappedComponent.name} is unmounted.`); } render() { return <WrappedComponent {...this.props} />; } } return WithLogging; }; // Example component const MyComponent = (props) => { return <div>{props.text}</div>; }; // Wrap MyComponent with withLogging HOC const MyComponentWithLogging = withLogging(MyComponent); // Usage const App = () => { return <MyComponentWithLogging text="Hello, world!" />; }; export default App;
最も一般的な React デザインパターンを使う
React は非常によく使われているライブラリであることが証明されており、このコミュニティは、オンラインで最も急速に成長しているデベロッパーコミュニティのうちの1つになっています。
また、react.js を簡単に学習して適応できる、便利な Web 開発リソースもオンラインで多数見つかります。
React のパワーは、その驚くべき機能とそれが提供する強固なアーキテクチャによるものであり、React の最も顕著で広く愛されている機能の1つに、そのデザイン パターンが挙げられます。
実際、デザインパターンで、このライブラリには並外れた実用性と有用性があり、コードの最適化とメンテナンスがしやすくなります。
そして、これでデベロッパーは本質的に柔軟性があり、パフォーマンスが上がり、保全がしやすいコードベースを生成できるアプリを作成できます。
これまで、ステートレス関数、レンダリングプロパティ、制御されたコンポーネント、条件付きレンダリング、反応フックなど、一般的な React デザインパターンについていくつかお話しました。
ただ、React デザインパターンは本記事で挙げたパターンだけに限定されるものではなく、実装できるデザインパターンがいくつかあることに注意が必要です。
よくあるデザインパターンの使い方に慣れてしまえば、他のパターンに移りやすくなるでしょう。
UXPin Mergeで React ベースのプロトタイプを構築しよう
React アプリケーション開発の本質を捉えるのは、適切なテクノロジーを使うことで簡単になります。UXPin Mergeでは、Reactコードコンポーネントを使って、強力なプロトタイプを構築でき、純粋なコードであるコードベースのプロトタイプを簡単にまとめることができます。こちらからぜひ無料でお試しください。