React学習メモ01

React学習メモ01

2023-09-21

公式ドキュメントにあるクイックスタート>Reactの流儀(https://ja.react.dev/learn/thinking-in-react)を読みながら知識を体系化していく

ユーザーインターフェースを考える際のReact

ReactでUIを構築する際には、UIをコンポーネントという部品に分割する。次に分割されたコンポーネントに関する視覚状態を記述。最後に複数のコンポーネントを接続し、それらの間をデータが流れるようにする。

1. JSONのような構造的なデータが既に存在する 2. デザイナーからUIのモックアップが用意されている

この状態を前提に話が進む。そもそもJSON APIが何か分からない

JSONとは

JSON(JavaScript Object Notation)は、軽量のデータ交換フォーマット。可読性が高く、マシンの構文解析や生成を行える形式となっている。その名の通りJavaScriptをベースに作られているが、言語から独立したテキスト形式、C,C++,C#,Java,Python等多くのプログラマにとってなじみが深いようです。

JSONの構造

JSONは2つの構造をもとにしている。 ・名前と値のペアの集まり。一般的な言語でもオブジェクト、レコード、構造体、連想配列などとして実現されている。普遍的なデータ構造であるが故に、ほぼすべてのプログラミング言語でサポートされている。そのためJavaScriptベースと言えども様々な言語で使われている。

JSONの形式

  1. オブジェクト オブジェクトは、順序付けされていない名前/値のペアのセット。オブジェクトは"{"で始まり、"}"で終わる。各名前の後ろには、":"(コロン)が付く。名前と値のペアは","(コンマ)で区切られる。以下の図は公式から 画像の貼り方わすれた
[  
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },  
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },  
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },  
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },  
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },  
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }  
]
  1. 配列 配列は、順序付けされた値の集まりである。配列は、"["で始まり、]で終わる。値は","(コンマ)で区切られる。 post03-2.png valueは、ダブルクォーテーションに囲まれた文字列、数値、真偽値、オブジェクト、配列で、これらの構造は入れ子にすることが可能。先程のオブジェクトの例で示したJSONはオブジェクトの配列であることが分かる。

  2. post03-3.png 一つの文字も文字列として扱われる

  3. 文字列 post03-4.png

  4. 数値 そんなに気にする必要もないので、後から困ったときに公式サイトを参照することにする。

参考文献: JSON(https://www.json.org/json-ja.html)

5stepでUIを実装する

ステップ1:UIをコンポーネントの階層に分割する。

方法論を取り敢えず確認していく。まず、モックアップの全てのコンポーネントとサブコンポーネントを四角で囲んで、それぞれに名前をつけていく。基本的なコンポーネントの分割方法は関数やオブジェクトを作成するときと同じ手法。一つのコンポーネントに対して一つの事を行う様に、コンポーネントが大きくなれば、それをサブコンポーネントに分割する。公式サイトにある画像をもとに考える。 post03-8.png まず、提出されたデザインの全体部分をアプリケーション全体のコンテナとして、上から順番に、検索バー、製品の情報を確認できるようなテーブル、その中に製品のカテゴリを示すための行と、そのカテゴリに属している具体的な製品を示す行が考えられる。自分は今後ブログの機能拡張やレイアウトをより洗練されたものにしようと思っているので、まずは、モックアップを作成し、それを画像のようにコンポーネントを分割する必要があるのだろう。一時期HTMLを書いていたことがあったが、HTMLをクラスごとに分けて骨子を作る過程と似ている。

上記画像の階層構造

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

ステップ2:Reactで静的なバージョンを作成する

上記のようなコンポーネントの階層が出来たら、アプリの実装に移る。その際にはインタラクティブで実装が複雑化すると予想される部分は一旦保留にして、静的な部分を先に実装し、その後にインタラクティブな要素を追加する。

コンポーネントを作成する過程では、他のコンポーネントを再利用し、親から子へデータを渡すためのPropsを経由して実装する。stateに関しては時間に対して変化するデータのためにあるものなので、このステップでは利用しない。

先程の階層構造のトップFilterbleProductTableからのトップダウン型の実装とProducctCategoryRow,ProductRowからのボトムアップ型の実装方法があるが、単純な構造の場合はトップダウン型の方が簡単に実装でき、大規模での実装の場合はボトムアップ型の方が進めやすいらしいです。今回は階層が単純なのでトップダウン型を利用するのが妥当だろう。

function ProductCategoryRow({ category }) {
	return (
		<tr>
			<th cloSpan="2">
				{category}
			</th>
		</tr>
	);
}

function ProductRow({product}) {
	const name = product.stoced ? product.name :
		<span style={{ color:'red'}}
			{product.name}
		</span>;
		// stocedに対しては真偽値が格納されていて、falseの場合は文字を赤くしよう。記法が分からない
	return (
		<tr>
			<td>{name}</td>
			<td>{product.price}</td>
		</tr>
	);
}

function ProductTalbe({ products }) {
	const rows = [];
	let lastCategory = null;
	//lastCategoryは何故必要なのか
	puroducts.forEach((product) => {
		if(product.category !== lastCategory) {
			rows.push(
				<Production 
					category={products.category}
					key={products.category} />
			);
		}
		row.push(
			<ProductRow 
				product={product}
				key={product.name} />
		);
		lastCategory = product.category;
	});
	return (
		<table>
			<thead>
				<tr>Name</tr>
				<tr>Price</tr>
			</thead>
			<tbody>{rows}</tbody>
		<table>
	);
}

function SerchBar() {
	return (
		<form>
			<input type="text" placeholder="Search..." />
			<label>
				<input type="checkbox" />
				{' '} //what is this?
				Only show products in stock
			</label>
		</form>
	);
}

fucntion FilterableProductTable({ products }) {
	return (
		<div>
			//コンポーネントの分割の真価
			<SearchBar />
			<ProductTable products={producuts} />
		</div>
	);
}

// de-ta
const PRODUCTS = [
  {category: "Fruits", price: "$1", stocked: true, name: "Apple"},
  {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
  {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
  {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
  {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
  {category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];

export default function App() {
	reutnrm <FilterableProductionTable products={PRODUCTS} />;
}

不明瞭な部分、分かってない部分

基本的に構造は理解できたが、コンポーネント内部の処理、JavaScriptの恐らくES6以降の書き方(アロー関数、三項条件式、配列においてHTMLはどのように格納されるか)とかがまだ慣れていない+理解できてないのでそこを学習。ProductTableコンポーネント内のforEachとアロー関数を併用した記法もよくわかってないので後から勉強する。

階層の最上位FilterableProductTableのpropsがの上から下に一方向に流れていく構造を単方向データフローと呼ぶようです。

ステップ3:UIの状態を最小限かつ完全に表現する方法を見つける

インタラクティブ性をアプリケーションに付与するにあたり、ユーザーがアプリの背後にあるJSONで表されるようなデータモデルを変更できる必要があり、そのためにstateを使用する。

stateについての説明(React公式より引用(https://ja.react.dev/learn/thinking-in-react))

state とは、アプリが記憶する必要のある、変化するデータの最小限のセットのことである、と考えましょう。state の構造を考える上で最も重要な原則は、DRY(Don’t Repeat Yourself; 繰り返しを避ける)です。アプリケーションが必要とする状態に関する必要最小限の表現を見つけ出し、他のすべてのものは必要になったらその場で計算します。例えば、ショッピングリストを作成する場合、商品のリストを配列型の state として格納できます。リスト内の商品数も表示したいという場合は、その数を別の state 値として格納するのではなく、代わりに配列の length を読み取ればいいのです。

ユーザーの行いたい動作に必要なデータのうち、変化するものでアプリ記憶する必要のある最小限の単位がstateというかんじだろうか。実装を行うにあたり、アプリケーションで扱われるデータの中で、それがstateに該当するか否かを判別する必要がある。判別の指標が公式サイトに提示されていた。

  • 時間経過に対して不変か否か
  • 親からprops経由で渡されるか否か
  • コンポーネント内の既存のstateやpropsに基づいて計算可能なデータか否か

ステップ4:stateを保存すべき場所を特定する

ステップ3にてアプリに必要な最小限のstateデータが特定された。ここで、stateを変更するコンポーネント、stateを所有するコンポーネントを特定する必要がある。その際の方法が公式サイトで提示されている。

  1. そのstateに基づいて何かをレンダーするすべてのコンポーネントを特定する。

post03-8.png 例で登場した製品情報が掲示されたサイトの例で考えると、ユーザーが検索窓に入力した情報はstateであり、また、チェックボックス内の在庫がある製品だけを表示するか否かのデータもstateである。ProductTableコンポーネントは検索された情報に関しては製品リストから表示するための製品リストをフィルタリングする必要があり、SearchBarコンポーネントは、stateを表示する必要がある。

  1. 階層内でそれらすべての上に位置する、最も近い共通の親コンポーネントを見つける。

ProductTableコンポーネントとSearchBarコンポーネント両者に共通してかつ上位に位置する親コンポーネントはFilterableProductTableコンポーネント以外にない。

3.stateがどこにあるべきかを決定する

先程のFilterableProductTableコンポーネントに値を保持させる。

実装フェーズ

コンポーネントにstateを追加するにはuseState()hooksを利用するようです。自分はReact Hooksをまだ理解していない。まず、stateを保持しておくべきと決めたFilterableProductTableコンポーネントに対してuseState()を利用してstateを追加する。

function FilterableProductTalbe({products}) {
	const [filterText, setFilterText] = useState('');
	const [inStockOnly, setInStockOnly] = useState(false);
}

追加したstateを子コンポーネントのProductTable,SearchBarコンポーネントにpropsとして渡す

<div>
	<SearchBar 
		filterText={filterText}
		inStockOnly={inStockOnly} />
	<ProductTable 
		products={products}
		filterText={filterText}
		inStockOnly={inStockOnly} />
</div>

これらの追加に加えて、フィルタリングの内部処理をProductTableコンポーネントないに加えたりする必要があるよう

ステップ5:逆方向のデータフローを追加する

現状だとprops,stateが階層構造の下方へのみ向かって流れている為、ユーザーから入力に従ってstateを変更できない状態である。そこで、上方向へのデータの流れをサポートする必要がある。そのために、テキストボックスで発生したイベントをトリガーとして上方向にデータが戻るようにする。具体的にはSearchBarコンポーネントの中でonChangeイベントハンドラを追加する。