1
/
5

Reactにおける状態のリフトアップとコンポーネント設計の重要性


目次

  • はじめに

  • 公式ドキュメントを参照してみる

  • 状態管理とコンポーネント設計の課題

  • 状態のリフトアップ(Lifting State Up)とは

  • なぜリフトアップが必要なのか

  • 状態のリフトアップを使った実装例

  • アプリケーションの構成

  • コード例

  • 親コンポーネント:App

  • 検索バーコンポーネント:SearchBar

  • フィルターコンポーネント:Filter

  • リスト表示コンポーネント:ItemList

  • 実装のポイント

  • サンプルコードにおける状態のリフトアップの適用

  • なぜこの設計が効果的か

  • 状態のリフトアップによるメリットまとめ

  • コンポーネント設計の重要性

  • 単一責任の原則

  • プレゼンテーションとロジックの分離

  • 状態管理を親コンポーネントに集約する必要性

  • まとめ

  • おわりに


はじめに

Reactを使ってアプリケーションを開発していると、コンポーネント間でデータを共有する必要が出てくることがあります。特に、複数のコンポーネントが同じデータに依存している場合、そのデータの管理方法が重要になります。

この記事では、状態のリフトアップ(Lifting State Up)というReactの基本概念と、コンポーネント設計における状態管理の重要性について、自分の学習経験をもとにまとめてみたいと思います。実際のコード例も交えながら、これらの概念を理解していきましょう。

公式ドキュメントを参照してみる

しばしば、いくつかのコンポーネントが同一の変化するデータを反映する必要がある場合があります。そんなときは最も近い共通の祖先コンポーネントへ共有されている state をリフトアップすることを推奨します。

公式ドキュメントではこのように記述されています。

state のリフトアップ - React
ユーザインターフェース構築のための JavaScript ライブラリ
https://ja.legacy.reactjs.org/docs/lifting-state-up.html


状態管理とコンポーネント設計の課題

Reactでは、状態(State)はコンポーネント内で管理されます。しかし、複数のコンポーネントが同じ状態に依存している場合、どのように状態を共有すればよいのでしょうか。

例えば、以下のようなシナリオを考えてみます。

  • 検索機能:ユーザーが入力した検索キーワードを使って、リストをフィルタリングする。
  • フィルタリング機能:複数のチェックボックスで項目を選択し、リストを絞り込む。
  • リスト表示:検索キーワードやフィルタリング条件に基づいて、結果を表示する。

この場合、検索バーやフィルタリング用のコンポーネントと、リスト表示用のコンポーネントがそれぞれ別々に存在しますが、同じデータ(検索キーワードやフィルタ条件)を必要とします。

状態のリフトアップ(Lifting State Up)とは

このような状況で有効なのが、状態のリフトアップという考え方です。状態のリフトアップとは、共通の状態を最も近い共通の親コンポーネントに移動させることで、複数の子コンポーネント間で状態を共有する方法です。

なぜリフトアップが必要なのか

  • データの一貫性:状態を一箇所で管理することで、データの整合性を保つことができます。
  • コードの明確化:状態がどこで管理されているかが明確になるため、コードの可読性が向上します。
  • バグの防止:データの不整合によるバグを減らすことができます。

状態のリフトアップを使った実装例

それでは、具体的なコード例を通して、状態のリフトアップを理解していきましょう。

アプリケーションの構成

  • 親コンポーネント(App
    • 状態を管理し、子コンポーネントにデータと関数を渡す。
  • 検索バーコンポーネント(SearchBar
    • ユーザーが検索キーワードを入力する。
  • フィルターコンポーネント(Filter
    • ユーザーがフィルタリング条件を選択する。
  • リスト表示コンポーネント(ItemList
    • 検索キーワードとフィルタ条件に基づいて、項目を表示する。

コード例

親コンポーネント:App

// App.js
import React from 'react'
import SearchBar from './SearchBar'
import Filter from './Filter'
import ItemList from './ItemList'

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchQuery: '',
selectedFilters: [],
items: [
{ id: 1, name: 'リンゴ', category: 'フルーツ' },
{ id: 2, name: 'バナナ', category: 'フルーツ' },
{ id: 3, name: 'キャベツ', category: '野菜' },
{ id: 4, name: 'ニンジン', category: '野菜' },
{ id: 5, name: 'チーズ', category: '乳製品' },
{ id: 6, name: 'ヨーグルト', category: '乳製品' },
]
}
}

setSearchQuery = (query) => {
this.setState({ searchQuery: query })
}

setSelectedFilters = (filters) => {
this.setState({ selectedFilters: filters })
}

render() {
const filteredItems = this.state.items.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(this.state.searchQuery.toLowerCase())
const matchesFilter =
this.state.selectedFilters.length === 0 || this.state.selectedFilters.includes(item.category)
return matchesSearch && matchesFilter
})

return (
<div style={{ padding: '20px' }}>
<h1>商品一覧</h1>
<SearchBar
searchQuery={this.state.searchQuery}
setSearchQuery={this.setSearchQuery}
/>
<Filter
selectedFilters={this.state.selectedFilters}
setSelectedFilters={this.setSelectedFilters}
/>
<ItemList items={filteredItems} />
</div>
)
}
}

export default App;

検索バーコンポーネント:SearchBar

// SearchBar.js
import React from 'react'

class SearchBar extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}

handleChange(e) {
this.props.setSearchQuery(e.target.value)
}

render() {
return (
<input
type="text"
placeholder="検索キーワードを入力"
value={this.props.searchQuery}
onChange={this.handleChange}
style={{ marginBottom: '10px', padding: '5px' }}
/>
)
}
}

export default SearchBar

フィルターコンポーネント:Filter

// Filter.js
import React from 'react'

class Filter extends React.Component {
categories = ['フルーツ', '野菜', '乳製品']

handleCheckboxChange (category) {
const { selectedFilters, setSelectedFilters } = this.props;
if (selectedFilters.includes(category)) {
setSelectedFilters(selectedFilters.filter(item => item !== category));
} else {
setSelectedFilters([...selectedFilters, category])
}
}

render() {
return (
<div style={{ marginBottom: '10px' }}>
{this.categories.map(category => (
<label key={category} style={{ marginRight: '10px' }}>
<input
type="checkbox"
checked={this.props.selectedFilters.includes(category)}
onChange={() => this.handleCheckboxChange(category)}
/>
{category}
</label>
))}
</div>
)
}
}

export default Filter

リスト表示コンポーネント:ItemList

// ItemList.js
import React from 'react'

class ItemList extends React.Component {
render() {
const { items } = this.props

if (items.length === 0) {
return <p>該当する商品がありません。</p>
}

return (
<ul style={{ listStyleType: 'none', padding: 0 }}>
{items.map(item => (
<li key={item.id} style={{ marginBottom: '5px' }}>
{item.name} ({item.category})
</li>
))}
</ul>
)
}
}

export default ItemList


これらのコードをファイルに記載するとブラウザ上では以下のように表示されます。


*実装の際はcodesandboxというツールを使用しました。



CodeSandbox
CodeSandbox is an online editor tailored for web applications.
https://codesandbox.io/dashboard/recent


実装のポイント

  • 親コンポーネントAppで状態を管理searchQueryselectedFiltersという状態を親コンポーネントで管理しています。
  • 子コンポーネントに状態と関数を渡すSearchBarFilterに状態と状態を更新する関数を渡しています。
  • データのフィルタリング:親コンポーネントでデータのフィルタリングを行い、その結果をItemListに渡しています。

サンプルコードにおける状態のリフトアップの適用

このサンプルコードでは、状態のリフトアップを以下のように適用しています:

  1. 検索クエリとフィルター状態の管理:
    • AppコンポーネントでsearchQueryselectedFiltersの状態を定義
    • これらの状態と更新関数を子コンポーネント(SearchBarFilter)に渡す
  2. 子コンポーネントの役割:
    • SearchBarFilterは自身で状態を持たず、親から渡されたpropsを使用
    • ユーザーの入力に応じて、親コンポーネントの状態を更新
  3. データのフィルタリングと表示:
    • Appコンポーネント内で状態を使用してアイテムをフィルタリング
    • フィルタリングされた結果をItemListコンポーネントに渡して表示

なぜこの設計が効果的か

  1. 単一の情報源: 検索クエリとフィルター選択の状態が App コンポーネントで一元管理されているため、アプリケーション全体で一貫したデータを使用できます。
  2. 明確なデータフロー: 状態の更新が App コンポーネントで行われ、その状態が子コンポーネントに渡されるという明確な流れがあります。これにより、データの変更と影響を追跡しやすくなります。
  3. コンポーネントの再利用性向上: SearchBarFilterItemList の各コンポーネントは、内部で状態を持たないため、異なる状況や他のプロジェクトでも再利用しやすくなっています。
  4. 状態の同期が容易: 検索とフィルタリングの状態が一箇所で管理されているため、これらの機能間でデータを簡単に同期できます。
  5. デバッグとテストの簡素化: 主要な状態とロジックが App コンポーネントに集中しているため、デバッグやテストが行いやすくなります。


状態のリフトアップによるメリットまとめ

  • データの一貫性:すべてのコンポーネントが同じ状態を参照しているため、データの不整合が起きません。
  • コードの見通しが良い:状態管理が一箇所に集約されているため、コードの構造が明確になります。
  • メンテナンス性の向上:機能の追加や変更が容易になります。

コンポーネント設計の重要性

Reactでは、コンポーネントの設計がアプリケーションの品質に大きく影響します。状態のリフトアップを活用しつつ、以下のポイントを意識すると良いでしょう。

単一責任の原則

各コンポーネントは一つの責任を持つように設計します。これにより、コンポーネントの再利用性が高まり、テストもしやすくなります。

プレゼンテーションとロジックの分離

  • プレゼンテーションコンポーネント:見た目の部分を担当し、状態を持ちません。
  • コンテナコンポーネント:データの取得や状態管理などのロジックを担当します。

今回の例では、SearchBarFilterItemListがプレゼンテーションコンポーネントに相当し、Appがコンテナコンポーネントとなっています。

状態管理を親コンポーネントに集約する必要性

私自身、実務で開発を進める中で、状態管理が各コンポーネントに分散していると、データの同期やバグの原因追跡が難しくなることを実感しました。状態を親コンポーネントに集約することで、これらの問題を解決でき、開発効率が向上しました。

まとめ

  • 状態のリフトアップは、複数のコンポーネント間で状態を共有するための基本的な方法です。
  • 状態を親コンポーネントに集約し、子コンポーネントに必要なデータと関数を渡すことで、データの一貫性とコードの可読性が向上します。
  • コンポーネント設計において、単一責任の原則プレゼンテーションとロジックの分離を意識すると、メンテナンス性の高いコードを書くことができます。

おわりに

Reactにおける状態管理とコンポーネント設計の重要性について、自分の学習経験をもとにまとめてみました。状態のリフトアップはReactの基本的な概念ですが、実際に手を動かして実装してみると、その効果を実感できます。

この記事が、同じようにReactを学んでいる方や、状態管理に悩んでいる方の参考になれば幸いです。



READY TO FASHIONでは一緒に働くエンジニアを募集しています!

ファッションに興味があるエンジニアのご応募お待ちしております!


Railsエンジニア
ファッション×採用を加速させる!Railsエンジニア募集!
2016年11月に創業。ファッション業界特化の求人プラットフォーム「READY TO FASHION」を開発・運営するスタートアップです。 弊社は、若者や既存の枠を超える「人」の力によって、旧態依然としたファッション業界の変革を促進し、「業界に従事する人」や「ファッションを楽しむ人」の生活を豊かにすることを目指しています。 そのために、採用におけるコミュニケーションだけでなく、業界のコミュニケーションにおける課題をITの力で解決し続けます。 【主な事業内容】 ・求人メディア事業 ファッション業界特化の求人プラットフォーム「READY TO FASHION」 https://www.readytofashion.jp 弊社のメイン事業。2017年3月にプロダクトをリリースし、現在は900社を超えるファッション企業、50000人以上のユーザーに登録していただいております。アパレル業界だけでなく、広義的にファッションを扱う企業情報を掲載。 ・CtoCマッチング事業 ファッションビジネスマッチングアプリ「fatch」 2019年2月ローンチ:https://www.readytofashion.jp/lp/fatch ・メディア事業 ファッション好きな若者のためのWEBマガジン「READY TO FASHION MAG」 https://www.readytofashion.jp/mag/ ・コミュニティー|学生マーケティング事業 ファッション系学生団体の組織「fashion community 1.0」 https://www.readytofashion.jp/mag/fashioncommunity1_0.html ・イベント事業 株式会社 三越伊勢丹と共催の『DENIM ACT NIGHT(デニムアクトナイト)』 https://www.readytofashion.jp/mag/news/denim_act_night_201803/
READY TO FASHION
フロントエンドエンジニア
ファッション×HRを加速させるフロントエンドエンジニア募集!
2016年11月に創業。ファッション業界特化の求人プラットフォーム「READY TO FASHION」を開発・運営するスタートアップです。 弊社は、若者や既存の枠を超える「人」の力によって、旧態依然としたファッション業界の変革を促進し、「業界に従事する人」や「ファッションを楽しむ人」の生活を豊かにすることを目指しています。 そのために、採用におけるコミュニケーションだけでなく、業界のコミュニケーションにおける課題をITの力で解決し続けます。 【主な事業内容】 ・求人メディア事業 ファッション業界特化の求人プラットフォーム「READY TO FASHION」 https://www.readytofashion.jp 弊社のメイン事業。2017年3月にプロダクトをリリースし、現在は900社を超えるファッション企業、50000人以上のユーザーに登録していただいております。アパレル業界だけでなく、広義的にファッションを扱う企業情報を掲載。 ・CtoCマッチング事業 ファッションビジネスマッチングアプリ「fatch」 2019年2月ローンチ:https://www.readytofashion.jp/lp/fatch ・メディア事業 ファッション好きな若者のためのWEBマガジン「READY TO FASHION MAG」 https://www.readytofashion.jp/mag/ ・コミュニティー|学生マーケティング事業 ファッション系学生団体の組織「fashion community 1.0」 https://www.readytofashion.jp/mag/fashioncommunity1_0.html ・イベント事業 株式会社 三越伊勢丹と共催の『DENIM ACT NIGHT(デニムアクトナイト)』 https://www.readytofashion.jp/mag/news/denim_act_night_201803/
READY TO FASHION
READY TO FASHIONからお誘い
この話題に共感したら、メンバーと話してみませんか?
READY TO FASHIONでは一緒に働く仲間を募集しています
1 いいね!
1 いいね!

同じタグの記事

今週のランキング

片桐 俊さんにいいねを伝えよう
片桐 俊さんや会社があなたに興味を持つかも