序論
フロントエンドはバックエンドに比べて自由度が高いですが、これは同時にフロントエンド開発者が低品質のコードを書きやすくなることも意味しています。フロントエンド技術の多様性と柔軟性により、開発者は機能実装やインターフェース設計でより多くの選択肢と変更スペースを持っています。プロジェクトの保守性、拡張性、再利用可能性および可読性を確保し、将来的な要求変化に備えるためにも。私たちはさまざまな技術や方法を採用して既存のコードアーキテクチャを改善し、コード品質と保守性を向上させることで高品質なコードを書くことができます。
その中でも重要な開発原則は「高凝集と低結合」です。これはモジュール内部の責任単一化及びモジュール間解耦合を強調します。関連する機能やロジックを一緒に整理することで、コードがより明瞭かつ理解しやすくメンテナンスし易いものになります。この記事では、「高凝集・低結合」 の技術原則についてわかりやすく詳しく説明します。
実際の事例を提供し、その原則をより良く理解し適用する手助けとします。
高凝集と低結合
高凝集と低結合はプログラミングにおいてよく見られる原則であり、保守性や拡張性のあるコードを書くために役立ちます。高凝集とは関連するコードをまとめて一つのタスクや機能を完成させることです。低結合はモジュール間の依存関係を最小限に抑え、メンテナンスや変更が容易になることを意味しています。
- 高凝集: 関連する機能やデータをひとつのモジュールまたはクラスにカプセル化すること、モジュールやクラス内の各要素が密接に連携し、協同して働くことで、高い凝集性を実現します。高凝集なコード構造は、コードの各部分間の結合度が低く、モジュールやクラス間の関係や依存関係が明確であることを意味し、これによりコードは理解しやすく、保守・拡張も容易になります。
- 低結合: 異なるモジュールやクラス間の関係及び依存を最小限に抑えることを指します。これによりモジュールまたはクラス間の関係が緩和され、相互影響が最小化されます。低結合なコード構造では、コードの各部分間の接触点および依存関係がシンプルで明確であり、そのため保守・拡張・リファクタリングも容易です。
高凝集を達成しつつモジュールまたはクラス間の結合度を下げることも可能ですし、低結合を実現しながらモジュールまたはクラスの凝集性を向上させることも可能です。
フロントエンド開発において、高凝集と低結合を実現する方法には【依存性注入】、【モジュール化開発】、【コンポーネント指向アーキテクチャー】、【単一責任の原則(SRP)】、【パブリッシュ-サブスクライブパターン(Pub/Sub)】および【インターフェース指向プログラミング(IoP)】などがあります。これらの方法を採用することで、高凝集かつ低結合なコード構造を実現しやすくなります。それによってコードはより明瞭で簡潔かつ保守しやすくなるため、可読性・拡張性・再利用可能性が向上します。これはソフトウェア開発の効率と品質の両方を高めることに寄与します。
実現技術と方法
高凝集と低結合を実現するために、以下の技術や方法を採用することができます:
単一責任
単一責任原則(SRP:Single responsibility principle)は単一機能原則とも呼ばれており、オブジェクト指向の五つの基本原則(SOLID)のひとつです。これはクラスが変更される理由はひとつだけであるべきだと規定しており、この原則の目的は複雑な問題をより小さな問題に分解することです。コードをよりメンテナンスしやすく、再利用可能にするために。
例えば、ウェブサイト上のログインと登録機能を取り上げてみましょう。私たちはログイン機能をLoginモジュールまたはクラスとして抽象化することができます。このLoginモジュールはユーザーのログインに関連するロジックだけを担当します。例えば、ユーザー名とパスワードの確認やログイン状態の記録などです。もしログイン機能がユーザー登録やパスワード回復など他の機能も含んでいる場合、これらの機能はそれぞれ異なるモジュールやクラスに封じ込めるべきであり、Login モジュール内に混在させるべきではありません。コード例は以下の通りです:
class Login {
constructor(username, password) {
this.username = username;
this.password = password;
}
validate() {}
recordLoginStatus() {}
}
class Register {
constructor(username, password, email) {
this.username = username;
this.password = password;
this.email = email;
}
validate() {}
createUser() {}
sendConfirmationEmail() {}
}
const login = new Login('Brycen', '12345678');
if (login.validate()) {
login.recordLoginStatus();
}
const register = new Register('Brycen', '12345678', 'demo@test.com');
if (register.validate()) {
register.createUser();
register.sendConfirmationEmail();
}
上記のコードでは、ログイン機能と登録機能をそれぞれ異なるクラスにカプセル化しています。Loginクラスはログイン関連のロジックのみを担当し、Registerクラスは登録関連のロジックのみを担当します。これにより、コードがより明瞭でメンテナンスやテストが容易になります。これらの機能を混在させた場合、コードは複雑でメンテナンスやテストが困難になる可能性があります。
上述したコード例に基づき、これら二つの機能を別々のコンポーネントとしてカプセル化することで、単一責任原則に従うことができます。以下は、Reactを使用して実装された簡単なビジネスシナリオの例です:
import React, { useState } from 'react';
function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleUsernameChange = (event) => {
setUsername(event.target.value);
};
const handlePasswordChange = (event) => {
setPassword(event.target.value);
};
const handleLogin = (event) => {
event.preventDefault();
/* ...... */
};
return (
<form onSubmit={handleLogin}>
<label>
<span>Username:</span>
<input type="text" value={username} onChange={handleUsernameChange} />
</label>
<label>
<span>Password:</span>
<input type="password" value={password} onChange={handlePasswordChange} />
</label>
<button type="submit">Login</button>
</form>
);
}
export default Login;
import React, { useState } from 'react';
function Register() {
const [email, setEmail] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleUsernameChange = (event) => {
setUsername(event.target.value);
};
const handlePasswordChange = (event) => {
setPassword(event.target.value);
};
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
const handleRegister = (event) => {
event.preventDefault();
/* ...... */
};
return (
<form onSubmit={handleRegister}>
<label>
<span>Username:</span>
<input type="text" value={username} onChange={handleUsernameChange} />
</label>
<label>
<span>Password:</span>
<input type="password" value={password} onChange={handlePasswordChange} />
</label>
<label>
<span>Email:</span>
<input type="email" value={email} onChange={handleEmailChange} />
</label>
<button type="submit">Register</button>
</form>
);
}
export default Register;
この例では、Login コンポーネントはログインフォームの表示とユーザーが入力したユーザー名とパスワードの処理だけを担当します。Register コンポーネントは登録フォームの表示と、ユーザーが入力したユーザー名、パスワード、メールアドレスの処理だけを担当します。これら二つのコンポーネント間でコードロジックは関連していません。
Reactで単一責任原則を実装する際に注意すべき点:
- コンポーネントの責任は可能な限り単一に保つことが望ましいです。一つのコンポーネントが多くの機能や責任を持ちすぎると、そのコード複雑度が増し、維持や拡張が困難になります。
- コンポーネントの状態は極力内部に留めておくことです。状態やロジックを複数のコンポーネント間で分散させるべきではありません。もし複数のコンポーネントが同じ状態を共有する場合は、その状態を共通親コンポーネントに移動させるか、またはグロバルな状態管理ツール(例:Redux)を使用して管理することです。
- コンポーネント間の通信は、可能な限りプロパティ(props)を介して行うべきであり、他のコンポーネントの状態に直接アクセスすることは避けるべきです。もしコンポーネント間で状態を共有する必要がある場合は、その状態を共通の親コンポーネントに持ち上げるか、またはグローバルな状態管理ツール(例:Redux)を使用して管理します。
- コンポーネントの命名は記述的であるべきであり、その責任と機能を明確に表現できるようにすべきです。
- もしコンポーネント内部の責任が複雑すぎる場合は、小さなサブコンポーネントに分割して単一責任原則に従いやすくすることが推奨されます。
- コンポーネントは外部状態を直接変更することを避け、代わりにコールバック関数等を用いて外部コンポーネントにメッセージを伝えることで再利用性や保守性向上させましょう。
- コンポーネントのライフサイクルメソッドでは主にレンダリング関連のロジックだけ扱い、多くのロジックがライフサイクルメソッド内で結合されてしまう事象から遠ざかりましょう。
- コンポーネントのスタイルは、機能と責任に密接に関連しているべきであり、過度に複雑または冗長なスタイルを避けることで、コードの可読性と保守性を向上させるべきです。
依存性注入
依存性注入(Dependency Injection, 略称 DI)はソフトウェア設計パターンの一つであり、オブジェクト間の依存関係管理をオブジェクト自体から外部へ移動することによって、モジュールやコンポーネント間の結合度を低減します。フロントエンド開発では、依存するオブジェクトをコンストラクターやメソッドへ引数として渡すことで実現されます。この方法はモジュールやコンポーネントをより柔軟かつテスト可能にし、具体的な実装への直接的な依存を減らします。
以下は、依存性注入を使用した簡単な例です:
class Container {
constructor() {
this.dependencies = {};
}
register(name, dependency) {
this.dependencies[name] = dependency;
}
get(name) {
if (!this.dependencies[name]) {
throw new Error(`${name} dependency not found`);
}
return this.dependencies[name];
}
}
class Service {
constructor() {
this.message = "Hello, World!";
}
greet() {
console.log(this.message);
}
}
class Controller {
constructor(service) {
this.service = service;
}
run() {
this.service.greet();
}
}
const container = new Container();
container.register("service", new Service());
container.register("controller", new Controller(container.get("service")));
const controller = container.get("controller");
controller.run();
ケーススタディでは、依存性注入コンテナ Container
オブジェクトを作成し、サービス Service
オブジェクトとコントローラー Controller
オブジェクトを定義しました。コントローラーはサービスに依存しており、私たちはコンテナ内でサービスとコントローラーを登録し、そしてその容器を使用してサービスの依存関係を注入します。この方法の利点は、サービスが必要な場所で手動でサービスインスタンスを作成する必要がなく、代わりに容器から取得することです。容器は自動的にサービスの依存関係を生成して注入します。
React では主にコンポネントのレンダリングや更新に焦点が当てられており、それ自体はネイティブな依存性注入機能を直接提供しないものです。しかし第三者ライブラリやカストマイズされた解決策を用いることで依存性注入特有の特徴実現可能です。React ContextはReactが提供するメカニズムであり組み込みツリー全体で層間データ伝達を行うために使います。この方法を利用して依存性注入を行うことで、コンポーネント間の依存関係を効果的に管理し、高い内聚性と低い結合度のコンポーネント設計を実現することができます。以下はReact Contextを使用して依存性注入を実装する例です:
// App.js
import React, { createContext, useContext } from 'react';
import { UserList } from './components/UserList.js';
import { UserService } from './services/user-service.js';
const userService = new UserService();
export const UserServiceContext = createContext(userService);
function Container() {
return (
<UserServiceContext.Provider value={userService}>
<UserList />
</UserServiceContext.Provider>
);
}
export default Container;
この例では、UserServiceContext
コンテキストオブジェクトを作成し、その値としてuserService
オブジェクトをProvider
コンポーネントに渡します。これにより、コンポーネントツリー内の任意の子コンポーネントでuseContext
フックを使用してuserService
オブジェクトにアクセスすることができます。以下はコード例です:
// UserList.js
import React, { useContext } from 'react';
import { UserServiceContext } from '../App.js';
export function UserList() {
const userService = useContext(UserServiceContext);
const users = userService.getUsers();
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}
このようにして、コンポーネント間で明示的にpropsを渡したり、ネストされたコンポーネントの階層を通じてデータを渡す必要がなくなります。コンテキストオブジェクトを作成することで、データや機能を上層のコンポーネントにカプセル化し、それらを下層のコンポーネントに提供して依存性注入パターンを実現することができます。
React Contextを使用して依存性注入を行う場合、いくつかのパフォーマンス上のオーバーヘッドが存在します。主な理由は、コンテキスト内のデータが変更された時にそのコンテキストに依存する全てのコンポーネントが再レンダリングされる必要があるためです。これは全ての子コンポーネントツリーを含む可能性があります。したがって、不必要なパフォーマンス負荷を避けるためにもContextの使用は慎重に行うべきです。以下はその例です:
import React, { createContext, useContext, useMemo } from "react";
const UserNameContext = createContext(undefined);
const UserAddressContext = createContext(undefined);
function App() {
return (
<UserNameContext.Provider value="Brycen">
<UserAgeContext.Provider value="18">
<Header />
<Content />
</UserAgeContext.Provider>
</UserNameContext.Provider>
);
}
function Header() {
const userName = useContext(UserNameContext);
return (
<section>
<header>
<h1>{userName}</h1>
</header>
</section>
)
}
function Content() {
const userName = useContext(UserNameContext);
const userAddress= useContext(UserAddressContext );
return (
<section>
<div>
<p>{userName}</p>
<p>{userAddress}</p>
</div>
</section>
)
}
機能に応じて UserContext
を UserNameContext
と UserAddressContext
に分割し、ユーザー名とユーザー住所が必要なコンポーネントでそれぞれ対応する Context を使用しています。この方法の利点は、データの伝達と更新をより細かく制御できるため、コードの保守性と柔軟性が向上します。
しかし、時には細分化しすぎることでパフォーマンスに影響を与える可能性もあります。パフォーマンスをさらに最適化するためには、React.memo() 関数を組み合わせて使用し、コンポーネントのメモ化処理を行うことができます。React.memo() は高階コンポーネントであり、コンポーネントのレンダリング結果をキャッシュするために用いられます。props が変更された場合のみ再レンダリングがトリガーされるため、不必要なレンダリングを防ぐことが可能です。
コンポーネントのパフォーマンスを向上させる。以下にサンプルコードを示します:
import React, { createContext, useContext, useMemo } from "react";
const UserContext = createContext(undefined);
function App() {
return (
<UserContext.Provider value={{
user: {
name: "Brycen",
address: "Earth"
}
}}>
<Header />
<Content />
</UserContext.Provider>
);
}
const UserInfo = memo((props) => {
const { user } = props;
return (
<div>
<p>{user.name}</p>
<p>{user.address}</p>
</div>
)
})
const Content1 = () =>{
const { user } = useContext(UserContext);
return (
<section>
<UserInfo user={user}/>
</section>
)
}
const Content2 = () =>{
const { user } = useContext(UserContext);
return useMemo(
() => (
<section>
<div>
<p>{user.name}</p>
<p>{user.address}</p>
</div>
</section>
),
[user]
);
}
パフォーマンスのオーバーヘッドを避けるために、以下の戦略を採用することができます:
- アプリケーション状態の更新を可能な限り小さな範囲に制限します。これにより、コンテキスト変更に関する通知が減少します。
- コンテキストオブジェクト内のデータ量をできるだけ減らします。コンテキストオブジェクト内のデータが多いほど、更新が必要なコンポーネントも増えます。したがって、本当に共有データが必要な場合のみContextを使用してください。
- Context のデータを複数の部分に分割します.Context内のデータ量が非常に大きい場合は、データを複数部分に分割し、それぞれ異なるContextを使用して伝達することも考えられます。データが変更されたときに、そのデータに依存するすべてのコンポーネントを再レンダリングする必要を避けることができます。
-
React.memo()
やshouldComponentUpdate()
を使用して不要なレンダリングを防ぎましょう。これらのメソッドは、コンポーネントの props が変化した際に、コンポーネントを再レンダリングするかどうかを決定します。コンテキストデータに依存するコンポーネントでは、useContext()
フックを使ってコンテキストデータにアクセスできます。これにより、React はコンテキストデータが変化した時だけ該当するコンポーネントを再レンダリングします。
モジュール化開発
モジュール化開発は、ソフトウェア開発の方法であり、大規模なシステムやアプリケーションを複数の独立した再利用可能なモジュールに分割することで、開発効率とコード品質を向上させます。各モジュールはそれぞれ固有の機能と責任を持ち、他のモジュールと相互作用し組み合わせることができて、システム全体の機能を実現します。
モジュール化開発により、複雑なシステムを多くの比較的独立したモジュールに分けることができます。各々のモジュールは特定の機能やビジネスロジックを担当します。この方法はコードの可読性・保守性・テスト容易性を高める助けになり、コード複雑度も低減されます。加えて、より良いコード再利用性も提供されます。既に開発およびテスト済みのモジュールが異なるプロジェクト間で再利用可能ですから、開発作業量や時間も削減されます。
例えば私たちが音楽プレーヤーアプリケーションを開発している場合、プレーヤーコントロールモジュール、音楽リストモジュール、そして歌詞表示モジュールが含まれています。もし私たちがモジュラー開発を採用しなければ、全ての機能を一つのファイルに書き込むことになり、コードが冗長でメンテナンスが困難になる可能性があります。しかしモジュラー開発を採用することで、私たちは各機能モジュールを独立したモジュールとしてカプセル化することができます。これにより高い内聚性と低い結合度の目標を実現します。
具体的なコード例は以下の通りです:
// 音楽再生関数
function play() {}
// 音楽一時停止関数
function pause() {}
// 音楽停止関数
function stop() {}
export default { play, pause, stop }
// 音楽リストをロードする
function loadPlaylist() {}
// リストに曲を追加する
function addSong(song) {}
// リストから曲を削除する
function removeSong(song) {}
export default { loadPlaylist, addSong, removeSong }
// 歌詞をロードする
function loadLyrics(song) {}
// 歌詞を表示する
function showLyrics(time) {}
export default { loadLyrics, showLyrics}
上記のサンプルコードでは、プレーヤーコントロールモジュール、音楽リストモジュール、歌詞表示モジュールをそれぞれ独立したモジュールとしてカプセル化しました。各モジュールは自身の機能と責任を持ち、いくつかのインターフェースを公開して他のモジュールが呼び出せるようにしています。これらの機能が必要な場所で、import を通じて関連するモジュールを導入し、その中のインターフェースを呼び出すことで関連する機能を実現します。
Reactを例に挙げると以下はシンプルなビジネスシナリオの例であり、どうやってモジュラー開発を使用するか示しています:
// PlayerControls.js
import React from 'react';
const PlayerControls = ({ isPlaying, onPlay, onPause, onStop }) => {
return (
<div>
<button onClick={onPlay} disabled={isPlaying}>再生</button>
<button onClick={onPause} disabled={!isPlaying}>一時停止</button>
<button onClick={onStop}>停止</button>
</div>
);
};
export default PlayerControls;
// MusicList.js
import React from 'react';
const MusicList = ({ musicList, selectedMusic, onMusicSelect }) => {
return (
<ul>
{musicList.map((music, index) => (
<li key={index} className={selectedMusic === index ? 'selected' : ''} onClick={() => onMusicSelect(index)} >
<span>{music.title}</span>
<span>{music.artist}</span>
</li>
))}
</ul>
);
};
export default MusicList;
// LyricsDisplay.js
import React from 'react';
const LyricsDisplay = ({ lyrics }) => {
return (
<div>
{lyrics.map((line, index) => (
<p key={index}>{line}</p>
))}
</div>
);
};
export default LyricsDisplay;
// PlayerPage.js
import React, { useState } from 'react';
import MusicList from './MusicList';
import LyricsDisplay from './LyricsDisplay';
import PlayerControls from './PlayerControls';
const PlayerPage = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [musicList, setMusicList] = useState([
{ title: 'Song 1', artist: 'Artist 1', url: 'song1.mp3', lyrics: ['Lyrics line 1', 'Lyrics line 2'] },
{ title: 'Song 2', artist: 'Artist 2', url: 'song2.mp3', lyrics: ['Lyrics line 1', 'Lyrics line 2', 'Lyrics line 3'] },
{ title: 'Song 3', artist: 'Artist 3', url: 'song3.mp3', lyrics: [] },
]);
const [selectedMusic, setSelectedMusic] = useState(null);
const handlePlay = () => {
setIsPlaying(true);
};
const handleStop = () => {
setIsPlaying(false);
setSelectedMusic(null);
};
const handlePause = () => {
setIsPlaying(false);
};
const handleMusicSelect = (index) => {
setIsPlaying(true);
setSelectedMusic(index);
};
const selectedMusicObj = selectedMusic !== null ? musicList[selectedMusic] : null;
const displayMusicLyrice = selectedMusicObj && selectedMusicObj.lyrics.length > 0
return (
<div>
{displayMusicLyrice && <LyricsDisplay lyrics={selectedMusicObj.lyrics} />}
<MusicList musicList={musicList} selectedMusic={selectedMusic} onMusicSelect={handleMusicSelect} />
<PlayerControls isPlaying={isPlaying} onPlay={handlePlay} onPause={handlePause} onStop={handleStop} />
</div>
);
};
export default PlayerPage;
この例では、プレーヤーコントロールモジュール、音楽リストモジュール、歌詞表示モジュールをそれぞれ PlayerControls
、MusicList
および LyricsDisplay
の三つの独立したコンポーネントにカプセル化し、props を通じてデータとイベント処理関数を伝達しています。PlayerPage
コンポーネント内でこれら三つのコンポーネントを導入し組み合わせることで、シンプルな音楽プレーヤーアプリケーションが実現されました。
注意すべき点は、モジュールのサイズに厳格な制限はなく、小さな機能単位でも大きな機能ブロックでも良いということです。重要なのはモジュールが単一責任を持ち独立して使用可能で再利用可能であることです。以下に簡単なコード例を見てみましょう。
function Container() {
return <div>Hello</div>;
}
function Entrance({ isRender }) {
return isRender ? <Container /> : null;
}
function UserInfoContext2() {
const isRender = false;
return (
<div>
<h1>User Information</h1>
<Entrance isRender={isRender} />
</div>
);
}
function UserInfoContext3() {
const isRender = true;
return (
<div>
<Entrance isRender={isRender} />
<h1>User Information</h1>
</div>
);
}
Entrance
関数のビジネスロジックは比較的シンプルですが、以下の特徴を持っていれば、一つのモジュールと見なすことができます:
1. 再利用性:Entrance
関数は異なるコンテキストで再利用可能であり、大幅な変更を加える必要がありません。
2. 単一責任:Entrance
関数は条件レンダリングを制御するだけであり、他のビジネスロジックに強く関連するコードを含んではいません。
3. 独立使用可能:Entrance
関数は独立したモジュールとして使用でき、他のモジュールやコンポーネントに依存しません。
このようなモジュラー設計はコードの保守性と再利用性を向上させ、理解や拡張が容易になります。どうやって一つのモジュールを定義するかについて通常考慮すべき点は以下です:
1. テスタビリティ:モジュール良好なテスタビリティを確保します。テストしやすいものでなければならない。モジュールは、他のモジュールや外部環境に依存せずに単体テストを行うことができるべきです。モジュールの境界は明確に定義されるべきであり、テストコードがモジュールの特定機能を対象に作成・実行されるようにする必要があります。
2. 範囲と責任:モジュールは明確な機能と責任を持つべきです。一つのモジュールがビジネスロジックの小さな部分をカバーしていても、それ自体で独立して動作し特定の問題を解決できれば良いです。ビジネスニーズやコード組織化の要求に応じて、模块的范围可以根据业务需求和代码组织的需要进行调整,可以是一个小的功能单元,也可以是一个更大的功能块。
3. 独立使用可能:ある模块は他の模块具体实现细节から独立した形式で使用可能であるべきです。各々の模块間では直接内部実装依存することなく、明確なインターフェースを通じた通信が行われるべきです。
4. 単一責任の原則:モジュールは単一責任の原則に従うべきです。つまり、1つのモジュールが1つの明確な機能を担当し、または1つの具体的な問題を解決することです。これにより、モジュールの理解性と保守性が向上し、コードがテストや再利用、拡張しやすくなります。
実際の開発では、ごく小さなビジネスロジックだけを担当するものをモジュールと見なすかどうかは、プロジェクト要件や設計目標に依存します。場合によっては、ビジネスロジックをさらに小さいモジュールへ分割することでコードの柔軟性や保守性が高まる可能性があります。しかし他方で、関連する複数のビジネスロジックを1つのモジュール内で組み合わせた方がコード管理や整理に適している場合もあります。
React で模块化開発を行う際は以下点に注意してください:
- コンポーネント間で密接なカップリングを避けるため propsを使用してデータおよびイベント処理関数を伝えます。
- サードパーティライブラリーやフレームワーク使用時、提供されたAPIとその使用方法を理解し、ベストプラクティスに従うべきです。
- 機能が単一のコンポーネントは、より小さなコンポーネントに分割することで、コードの保守性と再利用性を向上させることができます。
- コードをモジュール化して整理するために、例えばES6のimportやexport文法を使ってコンポーネントやモジュールをインポートおよびエクスポートします。
- 適切な命名規約を使用してコンポーネントやファイルに名前を付けることであり、PascalCase命名規約でコンポーネントに名前を付ける具体的なケースは後述します。
- コンポーネントの作成時には、そのライフサイクルや状態管理も考慮し、異なるライフサイクル段階や状態変化時に適切な操作が行えるよう配慮すべきです。
- コンポーネント内部ではpropsを直接変更しないよう注意してください。propsは読み取り専用と見なすべきです。もし props 内のデーター修正が必要な場合、親コンポーネントから子コンポーネントに渡されるべきコールバック関数を通じて実現するべきです。
オブジェクト指向プログラミング
オブジェクト指向プログラミング(Object-Oriented Programming、略称 OOP)は一般的なプログラミングパラダイムであり、現実世界の事物を個々のオブジェクトとして抽象化し、カプセル化、継承、多態性などの特徴を利用して高凝集と低結合のコードを実現します。オブジェクト指向プログラミングはデータと操作を組み合わせることに重点を置き、オブジェクトの作成やクラス定義及びオブジェクト間関係の構築によってアプリケーションを構築します。
オブジェクト指向方式では複雑なシステムも独立した各々が特定機能や状態管理責任を持つオブジェクトに分割可能です。これらのオブジェクトはメッセージ伝達で互いに通信・交流し、直接相手側内部データ操作する代わりです。このようなカプセル化および隔離性がコード更加模块化させます。理解しやすく、拡張とメンテナンスが容易です。
以下は、簡単なビジネスシナリオです。オブジェクト指向プログラミングを使用して、シンプルな学生情報管理システムを実装する方法を示します:
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
displayInfo() {
console.log(`Name: ${this.name}, Age: ${this.age}`);
}
}
class StudentManager {
constructor() {
this.students = [];
}
addStudent(student) {
this.students.push(student);
}
removeStudent(student) {
const index = this.students.indexOf(student);
if (index !== -1) this.students.splice(index, 1);
}
displayAllStudents() {
console.log('All Students:');
this.students.forEach((student) => student.displayInfo());
}
}
const studentManager = new StudentManager();
const student1 = new Student('Brycen 1', 18);
const student2 = new Student('Brycen 2', 18);
studentManager.addStudent(student1);
studentManager.addStudent(student2);
studentManager.displayAllStudents();
studentManager.removeStudent(student1);
studentManager.displayAllStudents();
この例では、Student
と StudentManager
の二つのクラスを定義しています。それぞれ学生と学生管理者オブジェクトを表します。各学生オブジェクトには自身の属性やメソッドがあり、たとえば name
、age
や displayInfo()
メソッドで学生の詳細情報を表示します。一方、学生管理者オブジェクトには学生の配列が含まれており、addStudent()
、removeStudent()
そして displayAllStudents()
などのメソッドで学生情報を追加・削除・表示することができます。
React を使用することで、私たちはこの学生情報管理システムを単純なコンポーネントツリーとして表現することが可能です。各コンポーネントは自分自身の属性やメソッドだけに注目し、他のコンポーネントの実装詳細について心配する必要はありません。以下は、Reactを使用して実装されたオブジェクト指向プログラミングの例に似た機能のサンプルです:
// Student.js
import React from 'react';
import PropTypes from 'prop-types';
const Student = (props) => {
const { name, age } = props;
return (
<div>
<p>name: {name}</p>
<p>age: {age}</p>
</div>
);
};
export default Student;
// StudentManager.js
import React, { useState } from 'react';
import Student from './Student';
const StudentManager = () => {
const [students, setStudents] = useState([]);
const handleAddStudent = () => {
const newStudent = { name: 'Brycen', age: 18 };
setStudents([...students, newStudent]);
};
const handleRemoveStudent = (index) => {
setStudents((prevStudents) => {
const newStudents = [...prevStudents];
newStudents.splice(index, 1);
return newStudents;
});
};
const displayAllStudents = () => {
students.forEach((student) => console.log(student));
};
return (
<div>
<button onClick={handleAddStudent}>Add Student</button>
<button onClick={displayAllStudents}>Display All Students</button>
{students.map((student, index) => (
<div key={index}>
<Student {...student} />
<button onClick={() => handleRemoveStudent(index)}>Remove</button>
</div>
))}
</div>
);
};
export default StudentManager;
例で、私たちはStudent
とStudentManager
コンポーネントを定義して、学生と学生マネージャーを表しました。私たちはuseState
フックを使用して学生の配列を管理し、新しい学生を追加するための handleAddStudent()
メソッドや、学生を削除するためのhandleRemoveStudent()
メソッド、すべての学生情報を表示するdisplayAllStudents()
メソッドも使用しています。またmap()
メソッドも使って学生配列に対応した Student コンポーネントが各々レンダリングされるようにし、そこではそれぞれの詳細情報と削除ボタンが表示されます。
React でオブジェクト指向プログラミング(OOP)実装時には注意すべき点があります。以下はその一般的な注意事項です:
1. クラスによるコンポーネントの定義:Reactでは、関数またはクラスを使用してコンポーネントを定義することができます。オブジェクト指向プログラミングの考え方でコードを整理したい場合は、クラスによるコンポーネントの定義が推奨されます。なぜなら、クラスはカプセル化や継承といった特性を持ち、オブジェクト指向プログラミングに適しているからです。
2. 単一責任原則(SRP)の遵守:オブジェクト指向プログラミングでは、一つのクラスは一つの責任だけを持つべきです。すなわち、一つの機能だけに責任を持ちます。同じくReactでも、一つのコンポーネントは一つの責任だけを持っており、ただ一つの機能に対応するべきです。
3. コンポーネント状態とメソッドのカプセル化:オブジェクト指向プログラミングではカプセル化が強調されています。そのため、コンポーネント内部状態やメソッドもカプセル化されるべきです。これにより内部状態が混乱し予測不可能になることを防ぎます。state と props を利用してコンポーネント状態やメソッドをカプセル化しましょう。そうすることで保守性や再利用性が高まります。
インターフェース指向プログラミング
インターフェース指向プログラミング(Interface-Oriented Programming、略称IOP)もまた一つのプログラミングパラダイムです。インターフェースはオブジェクト間の相互作用と通信の契約と見なされており、オブジェクトが持つべきメソッドや属性、あるいは振る舞いを定義しています。インターフェースを使用することで、共有された規範と制約のセットを定義し、異なるオブジェクトがこれらの規範に従って交流することが可能になりますが、具体的な実装詳細には関心を払わずに済みます。
実際には、インターフェース指向プログラミングはオブジェクト指向プログラミングを補完し拡張する形で利用できます。インターフェース指向プログラミングのキーとなる考え方は具体的な実装ではなくインタフェス依存です。インタフィス指向コードを書くことで私たちは具体的実装から注意点を移動させてインタフィス定義上へ集中させることが出来ます。それによってオブジエクト間结合度低下させられます。
以下は簡単なコード例です:
// インターフェースを定義する
interface ILogger {
log(message: string): void;
}
// インターフェースを実装する具体的なクラス
class FileLogger implements ILogger {
log(message: string) {
// ログをファイルに書き込む具体的な実装
console.log(`[File logger] ${message}`);
}
}
class ConsoleLogger implements ILogger {
log(message: string) {
console.log(`[Console logger] ${message}`);
}
}
// インターフェースをパラメーターの型として使用する
function processLogger(logger: ILogger, message: string) {
logger.log(message);
}
// 具体的なログ記録器のインスタンスを作成する
const fileLogger = new FileLogger();
const consoleLogger = new ConsoleLogger();
// 関数を呼び出し、異なるロガーを渡す
processLogger(fileLogger, "Error occurred.");
processLogger(consoleLogger, "Hello, World!");
実際のところ、開発時にはTS(TypeScript)の宣言を多く書くことになります。any 開発に向けてではなく。
統一命名規則とコードスタイル
「統一命名規則とコードスタイル」とは、フロントエンド開発において、コードの可読性と保守性を高めるために、チームメンバー間で互いのコードを迅速に理解し修正することができるよう、統一された命名規則とコードスタイルを定めることです。
この規則では、業界で広く認知されているBEM(Block-Element-Modifier)命名規則参考します。これはCSSクラスの命名に用いられる一般的な方法であり、保守可能かつ再利用可能なスタイルのコードを作成するために使用されます。以下はBEM命名規則の基本原理及び例示です:
- ブロック(Block):独立して再利用可能なコンポーネントやモジュールを表します。ブロックは記述的な名称を持ち、単語間はハイフン(-)で区切ります。例えば、「navbar」という名称がナビゲーションバーに使われます。
<div class="navbar"><!-- content --></div>
- 要素(Element):ブロック内の構成要素で、特定のブロックの文脈でのみ意味を持ちます。要素名はダブルアンダースコア(_)を使用してブロックと区切ります。例えば、ナビゲーションバー内のメニュー項目は "navbar__item" と命名されるかもしれません。
<div class="navbar">
<a class="navbar__item" href="#">Home</a>
<a class="navbar__item" href="#">About</a>
<a class="navbar__item" href="#">Contact</a>
</div>
- 修飾子(Modifier):ブロックまたは要素の外観、状態、または振る舞いを変更するために使用します。修飾子名はシングルアンダースコア(_)を使用してブロックや要素と区切ります。例えば、ナビゲーション項目が活性化状態であれば "navbar__item--active" のような修飾子が付けられることがあります。
<div class="navbar">
<a class="navbar__item navbar__item--active" href="#">Home</a>
<a class="navbar__item" href="#">About</a>
<a class="navbar__item" href="#">Contact</a>
</div>
BEM命名規則を用いることでスタイルシートの可読性や保守性が向上します。これによって開発者はコンポーネント間の関係を明確に理解し、スタイリング衝突の可能性を減少させる助けになります。
CSS Modulesを使用する際、この命名規則はいくつかの衝突が発生します。スタイル命名の可読性と関連性を解決するために、CSS Modulesでは通常アンダースコア(*)*を使って単語を繋げます。これはBEMで使われるダブルアンダースコア(__)による要素分割と命名衝突を引き起こします。実際の状況に合わせて、私はブロックとエレメントをアンダースコア()で繋ぐ単語に変更しました。
例えば、シンプルなビジネスシーンで考えてみましょう。電子商取引サイト開発中だと仮定して、商品リストとカートコンポーネントが含まれています。統一された命名規範やコードスタイルに従うために、以下の方法でコードを書くことができます:
import React, { memo } from 'react';
import styles from './styles.module.css';
import ProductItem from './ProductItem';
function ProductsList(props) {
const { products, onAddToCart } = props;
return (
<div className={styles.products}>
<h2 className={styles.products_title}>Products List</h2>
<ul className={styles.products_list}>
{products.map((product) => (
<ProductItem key={product.id} product={product} onAddToCart={onAddToCart} />
))}
</ul>
</div>
);
}
export default memo(ProductsList);
この例では、コンポーネントのクラス名が二つの部分:ブロック(products)、エレメント(title\list)に分けられます。ブロックはコンポーネントの名称を表し、エレメントはその内部のサブエレメントです。修飾子はコンポーネントの状態や変化を表します。同時に、私たちは単一責任の原則を使用し、「商品リストの表示」と「商品アイテムの表示」をそれぞれ別々のコンポーネントにカプセル化しました。
import React, { memo } from 'react';
import styles from './styles.module.css';
function ProductItem(props) {
const { product, onAddToCart } = props;
const handleAddToCart = () => {
onAddToCart(product);
};
return (
<li className={styles.products_item}>
<div className={styles.products_item__header}>
<div className={styles.item_header__name}>
{product.name}
</div>
<div className={styles.item_header__date}>
{product.date}
</div>
</div>
<div className={styles.products_item__description}>{product.description}</div>
<button className={styles.products_item__button} onClick={handleAddToCart}>Add to Cart</button>
</li>
);
}
export default memo(ProductItem);
この例では、商品アイテムのクラス名は二つの部分に分けられています:ブロック(products)、エレメント(item\header)、修飾子(header name\date\description button)。また、単一責任原則を用いて、「商品アイテムの表示」と「ショッピングカートへ追加するロジック」をそれぞれ別々のコンポーネントにカプセル化しました。次に、注釈スタイルを統一してコードの可読性と保守性を向上させる必要があります。
コメント規約に関しては、以下の原則に従うべきです:
- 外部変数や関数はブロックコメント(/ * ... * /)で注釈されるべきです。
このコメントスタイルは通常、外部に公開されるコード要素(公開されたインターフェースやグローバル変数など)に使用されます。ブロックコメントは、明確なコメント構造と識別しやすいマークを提供することができます。 - 内部の変数や関数については、状況に応じて単一行コメント(//)またはブロックコメント(/* ... */)を使用することができます。内部のコードブロックでは、必要に応じて適切なコメント方法を選択します。単一行コメントは短い注釈に適しており、ブロックコメントは複数行の詳細な注釈に適しています。
- コメントの目的はビジネスシナリオ、コードロジックまたは特定の実装ディテールを説明し、コードの可読性と理解度を高めるためです。したがって無効なコメントを追加することを避け、意味ある側面へ焦点を当てるべきです。
//【最適化前】
// 批量共有かどうか
const isBatchSharing = !meetingId;
// 権限が無効かどうか
const disabled = isBatchSharing && !isVip;
// ホストであるかどうか
const isHost = isBatchSharing || meetingRole === MEETING_ROLES.host;
//【最適化後】
/** 会議IDが存在しない場合、批量共有ということです */
const isBatchSharing = !meetingId;
/** ホスト権限あり - 批量共有の場合、自分の会議を操作するか、または会議詳細ページでホスト役割を担うユーザーだけが操作権限を持っています。 */
const hasHostPermission = isBatchSharing || meetingRole === MEETING_ROLES.host;
// 操作権限の無効化 - バッチ共有時にユーザーがVIPではない場合、共有操作を無効にし、ユーザーにVIPへのアップグレードを要求します。
const disabledPermission = isBatchSharing && !isVip;
/** 📌 モーダルダイアログのオープンイベントを監視する */
const listenOpenDialogEvt = useMemoizedFn((e) => {
const { meetingIds, calendarInvitationList } = e.detail;
// 开启弹窗
setOpen(true);
// 共有される会議IDリスト
setShareMeetingIds(meetingIds);
// 招待可能なカレンダーのメールボックスを組み立てる
// 複数共有では具体的な会議のカレンダーイベントは取得しないため、招待可能なカレンダーのメールボックス集は存在しない、故デフォルトで空の配列を取得する
setCalendarInviteList(calendarInvitationList?.map((email) => ({ email })) || []);
});
コードレビューを行う際には、コメントの形式と位置をチェックし、コメントがコードの意図と機能を正確に説明していることを確認する必要があります。また、コメントはクリアで簡潔なものであるべきです。技術的な詳細や関係のない情報が多すぎると、コードの可読性が低下します。
命名規則やコーディングスタイルを統一することで、コードはよりクリアで読みやすく保守しやすくなります。チームメンバー全員が同じ規範とスタイルに従うことで、協力効率が向上し、コードの競合やエラー発生の可能性も減少します。
定期的なコードリファクタリングおよび最適化
品質保持およびメンテナンス可能性を高めるためには定期的なコードリファクタリングおよび最適化作業が不可欠です。
コードリファクタリングでは機能変更せずに、改善されたプログラム構造や品質向上等によって読み易さ及び保守容易性を高めています。
コード最適化は、コードのパフォーマンスを改善し、リソース消費を減らすなどの方法でコード品質を向上させるプロセスです。私たちは容易に当時のビジネスシーンや能力に限定されてしまい、十分に優れたコードが書けないことがあります。要求が継続的に反復する中で、より多くの問題が露呈してきます。したがって、警戒を怠らず、コードリファクタリングや最適化の機会を見極める必要があります。機会があれば、問題点や制約に対処して改善し、コードの品質・可読性・保守性・パフォーマンスを高めると同時に将来的なニーズや変更へ準備するべきです。
開発前の準備作業
フロントエンド開発ではUIデザイン構造のレビューは非常に重要であり、開発者が問題を迅速に特定し解決する手助けとなりプロジェクト全体の品質向上につながります。そのため開発前準備作業ではフロントエンド開発者はビジネス要件とデザイン原案を丁寧に分析する必要があります。
ビジネスロジックとUIデザインの具体的な要求を理解するためには、ページの構造、要素、インタラクション、動きのエフェクト、レスポンシブデザインなどの内容を含めています。同時に、要件の不確実性や非合理性などの問題にも注意が必要です。
開発者が自分自身で要件やプロトタイプへの理解が製品やデザインの期待と一致しないことを発見した場合は、関連する人員とすぐにコミュニケーションを取り最終結果を確認する必要があります。このようにしてビジネスニーズと設計プロトタイプへの準拠を開発過程で保証し、同時にプロジェクト全体の品質向上も図れます。
プロトタイプ図面布局構造
ページ基本構造及びエレメント確認後は更に進んでページ布局構造決定します。これはページコンテナー配置方式・グリッドシステム利用・各コンテナー間関係等含みます。布局構造決定際では 階層構造・内容配分・アクセシビリティ・メンテナビリティ及びレスポンシブデザイン等因子考量必須です。同時に、ページの美観性とユーザー体験も重要です。
ページの保守性と拡張性を確保するためには、一定のデザイン規範や標準に従うべきです。例えば意味的なHTMLタグを使用し、入れ子が深すぎるタグを避ける、モジュール化されたCSSやJavaScriptを採用するなどです。さらに、異なるデバイスでの表示効果も考慮し、それに対応するためレスポンシブデザインを採用して異なるデバイスや画面サイズに適応します。プロトタイプ図のレイアウト構造を確認することで、ページの基本フレームおよび構造が設計要求に合致していることを保証し、後続の開発作業へ基盤支援提供しつつユーザー体験とページパフォーマンスも最適化できます。
機能交互及び動的効果分析
次に、ページの機能的なインタラクションと動きについてさらに分析します。これはページエレメントのインタラクティブ方法・アニメーション効果・トランジション効果等を含みます。あなたはこれらのインタラクションと動きの具体的な要件を理解し記録する必要があります。例えばボタンのクリック効果、フォーム検証効果、ページ切り替え効果やドロップダウンメニュー效果等です。同時に、インタラクションド動作がユーザー体験および **性能へ与える影響も考慮して, ページのイントラクショント力強くスムースで性能も最適化されていることを確認しなければなりません。
デザイナーは交互作用やアニメーショングルフィックスをデザイナーす際に一定原則や規範守るほうが良いでしょう。
たとえば、過度なアニメーション効果の使用を避ける、スムーズで自然なトランジション効果を採用する、さまざまなデバイスやネットワーク環境の性能制限を考慮するなどがあります。また、異なるユーザーのニーズや習慣にも配慮し、そのためにはユーザーリサーチやテストを行い、インタラクションと動きの設計がユーザーの期待と要求に合致していることを確認します。
機能のインタラクションと動きを分析することで、ページにより多くのインタラクティブ性と楽しさを加えて、利用者体験や満足度向上させるだけではなく、ページがより魅力的かつ竞争力あるものへ変わっていきます。
需給デザインの妥当性確認
ページ各部分へ求められている事項分析後は再度ビジネス需要およびデザインプロトタイプ見直し必要です。不確実性または非合理性存在していればチーム及び関連人員良好コミュニケーション保ち協働進めます。これには製品マネージャー・UI/UXデザイナー・バックエンド開発者等交流含みます。各方面のニーズとデザインが十分に理解され、考慮されていることを確認します。同時に、必要であればチームメンバーとコミュニケーションを取り問題を解決することも重要です。これによりプロジェクトの全体的な効率と品質が向上し、開発過程でビジネスニーズやデザインプロトタイプの要求に合致していることを保証し、また計画の設計や開発スケジュールを迅速に調整して、プロジェクトの要求に更に良く応えられるようにします。
さらに、需給デザインの妥当性を確認する際は、拡張性や保守性なども考慮すべきです。例えばデータ構造やインターフェース設計では将来的な需要変化やデータ処理複雑度へ対応可能かどうかが重視されます。適切なデータ構造およびインターフェース設計採用することでコード拡張性及び保守性が高まり後続開発およびメンテナンス費用削減可能です。その他コード読み易さ及びテスト容易さも注目すべき点であり明瞭かつわかり易いコード構成およびコメント使用は可読性及びメンテナンス向上させます。
業務コンポーネントの分割と組み合わせ
ページの基本構造や要素を確認し、ページの機能交互や動き効果を分析した後、類似する機能モジュールを独立したコンポーネントに分けることが必要です。これにより各コンポーネントの責任が明確になります。この過程では、コンポーネントの再利用性、保守性、拡張性だけでなく、コンポーネント間の関係や依存も考える必要があります。同時にプロジェクトのニーズとデザイン原型に基づいて、コンポーネント具体的な実装方法とスタイルも決定しなければなりません。
UIデザイン原型のレイアウト構造を分析することでどの要素をコンポーネント化すべきか判断し、それらを相互関係や依存関係に応じて組み合わせます。実際開発では既存のライブラリまたは自作したコンポーネート使用してビジネスニーズを満たすことが可能ですし充分なテストやデバッグも行い異なるシナリオ下でその互換性や安定性保証します。ビジネス・カムパウメント区切りは開発者がより良く代码资源整理する手助けします。コードの可読性と保守性を向上させることで、重複するコードを減らし、開発効率も高めることができます。
例えば、ある電子商取引サイトには商品リストページとショッピングリストページがあります。
商品リストページでは、フィルターコンポーネント、ソートコンポーネント、商品リストコンポーネント、商品リストアイテムコンポーネントを分割することができます。フィルターコンポーネントには、価格帯、ブランド、カテゴリーなどのオプションが含まれます。 ソートコンポーネントには、価格、売上、評価などのソートオプションが含まれます。商品リストコンポーネントには、商品リスト、ページングなどが含まれます。 商品リスト項目コンポーネントには、商品名、価格、画像などが含まれます;
ショッピングリストページでは、ショッピングカート商品リストコンポーネントとショッピングカート商品アイテムコンポーネントを分割することができます。 ショッピングカート商品リストコンポーネントには、ショッピングカート商品リスト、合計価格、チェックアウトなどが含まれます。 ショッピングカート商品項目コンポーネントには、商品名、価格、数量、小計などが含まれます。
このように、各コンポーネントは異なるページで再利用することができ、アプリケーション全体に影響を与えることなく、必要に応じて修正や拡張を行うことができます。例えば、商品リストページのフィルタリングやソートコンポーネントは、ショッピングリストページでも使用することができます。 ビジネスコンポーネントを分割することで、コードとリソースをよりよく整理し、コードの可読性と保守性を向上させ、重複コードを減らし、開発効率を向上させることができます。
React フレームワークの特徴を使用する
ビジネスコンポーネントを分割した後、ビジネス要件とコンポーネントの機能に基づいて、適切なReactの特徴や技術を選択してこれらのコンポーネントを実装します。ビジネスプロセスの実施中には、以下のいくつかの点を考慮する必要があります:
- コンポーネントの再利用性と保守性
Reactはコンポーネント指向フレームワークであり、その再利用性と保守性は非常に重要です。コンポーネント設計時には、可能な限り相互依存関係を避けるべきであり、共通ロジックを抽象化し、再利用可能なHookやユーティリティ関数としてカプセル化することで冗長なコード量を減らすべきです。また、容器(Container)コンポーネントやUI コンポーネントおよびビジネス コンポ―メント等に分けることでそれぞれ明確な役割分担が行えるため保守・拡張が容易になります。- コンテナコンポーネント:コンポーネントの状態とロジックを管理する責任があり、通常はクラスコンポーネントで実装されます。コンテナコンポーネントはpropsを介して状態とロジックをUIコンポーネントやビジネスロジックコンポーネントに渡し、UI、状態、およびビジネスロジックの分離を実現します。一般的に、コンテナコンポーネントはUIレンダリングロジックを含まず、データやイベント処理関数をprops経由でUIコンポーネントやビジネスロジックコンポーネントに伝えます。
- ビジネスロジック:特定のビジネス機能の実装を担当し、通常はいくつかのメソッドと関数を含む。 ビジネス・ロジック・コンポーネントは、コンテナ・コンポーネントからpropsを通じて渡されたステートとロジックのデータを受け取り、これらのデータを処理して操作し、最後に処理されたデータをUIコンポーネントに渡してレンダリングする。
- UI コムパウェア:コンテナ・コンポーネントやビジネス・コンポーネントが提供するデータやイベント・ハンドラを、ユーザーから見えるUI表示に変換する。 通常、ビジネスロジックは含まず、UIのプレゼンテーションとインタラクションに重点を置く。
コンテナコンポーネント、ビジネスコンポーネント、およびUIコンポーネントに分けることで、各コンポーネントの責任が明確になり、メンテナンスや拡張が容易になります。また、異なるシーンで再利用しやすくするためのコンポーネントの再利用性も向上します。実際の開発では、具体的なシーンや要求に応じて適切なコンポーネントタイプを選択して組み合わせや設計を行います。
コンテナコンポーネント、ビジネスコンポーネント、およびUIコンポーネントに分けることで、各コンポーネントの責任が明確になり、メンテナンスや拡張が容易になります。また、異なるシーンで再利用しやすくするためのコンポーネントの再利用性も向上します。実際の開発では、具体的なシーンや要求に応じて適切なコンポーネントタイプを選択して組み合わせや設計を行います。
-
コンポーネントのデータフロー設計
Reactではデータフローは一方向です。親コンポーネントから子コンポーネントへ流れます。ビジネスコンポーネントを実装する際は、必要とされるデータと状態及びデータフロー設計を考慮する必要があります。状態が複数のコンポーネント間で散在しないようにするためにも、可能な限り状態管理を集中させてデータフローを明確化すべきです。状態管理とテストをより便利に行うことができ、コンポーネントの分割や再利用も容易になります。
コンポーネント間で状態を共有する必要がある場合は、React の
Context
やRedux
などの状態管理ライブラリを使用することができます。Context
はコンポーネント階層を超えてデータを共有するメカニズムであり、props を介して一段階ずつ渡すことなく、コンポーネントツリー内の任意のコンポーネントにデータを伝達することが可能です。しかし、Context は全てのケースに適しているわけではありません。それは React の単方向データフローのモデルを壊し、データフローが予測不可能になるからです。アプリケーションの状態が複雑な場合は Redux などの状態管理ライブラリの使用を考えることもできます。Redux は予測可能な状態コンテナであり、アプリケーションの状態を集中的に管理し、完全なステートマネジメントソリューションセットを提供します。
状態更新、非同期処理、ミドルウェアなどを含む。
通常の順序に従って、まず要件文書を確認し、今回の要求の全体的な目標と機能を明確に理解した後で、設計されたUIプロトタイプ図を見るべきです。個人的には実際にUIプロトタイプ図から見始める方が好きでして、その後で要件文書を見る時にはより明瞭なイメージが持てます。要求をより簡単に理解できます。
おわりに
以上のことから、「高凝集と低結合」の開発原則に従い、開発前に十分なコミュニケーション、分析およびタスクの分解を行うことで、異なる責任を持つビジネスコンポーネントを設計し、良好な構造のデータフローを構築することができます。このアプローチは、可読性が高く保守・拡張が容易なビジネスコンポーネントの構築に寄与し、プロジェクト全体の品質と開発効率を向上させる助けとなります。
創作チーム
作者:Brycen
校閲:Yuki