react入門4
関数コンポーネントの設計指針
関数コンポーネントは、というよりプログラム全般に言えることですが、コンポーネントあるいはモジュールあるいは一つのソースファイルを見たときに、意味が読み取れるシンプルさが求められます。
再利用の観点からのシンプルさ
同じことを2度記述しないことは、プロジェクト全体を見たときに、シンプルさにつながります。また、他のプログラムファイルやコードに依存しないこともシンプルさにつながります。また、独自に定義した入出力ではなく、広く知られている入出力の型、たとえば数値や文字列だけで関数やモジュールを定義することもシンプルさにつながります。
このような観点から他への依存性を低くした、プログラム視点でのシンプルさを追求すると、再利用性の高いコンポーネントあるいはモジュールが出来上がります。一般にライブラリと言われるものです。
アプリケーションを構築するユーザー側のエンジニアはそれほど再利用性を意識しない方が良い場合が多い印象です。
現実世界のモデル化の観点からのシンプルさ
一方で、実世界は複雑です。たとえば人には名前や年齢、容姿、声など様々な属性があります。買い物の取引でも、買った人、売った人、取引された場所、値段、商品名、仕様、納期など、様々な属性で取引をモデル化し表現する必要があります。モデル化の程度は、ソフトウェアで実現したい目的で決まります。そして、詳細な実現手段は往々にして時間と共に変化します。このような複雑な実世界の事柄をシンプルに語るときには、上記のプログラム的なシンプルさとは異なる、モデル化、概念化というシンプルさが重要になってきます。たとえば、家を建てるときは、間取りを考え、お風呂場やトイレ、キッチンに使用するドアや器材を選定します。このとき、たとえばドアを構成する金具や材質の詳細は考えていないはずです。間取りを検討しているときには、ドアの詳細をイメージすることは思考の妨げになります。また、ある程度間取りが決まってくると、家具や建具は同じ色合いにしたい、間取りによりドアの位置や照明の数が決まるなど、具体的、詳細要素の検討に入り、抽象的な間取りと、具体的な建具の検討は、相互に関係しているはずです。
アプリケーションを構築する際には、どのあたりに変更可能性があるかを考えて、構造を決めます。
よくあるパターン
再利用の観点と、現実世界のモデル化の観点のシンプルさを結びつける例として、家という概念の構成は下のような図で何となく理解していただけるかと思います。シンプルな素材から様々な建具が生まれ、家を建てるときには要望から間取りや、間取りごとに建具を選択します。
Reactなどライブラリを使って何らかのサービスを開発するユーザサイドのプログラム開発の場合、家から間取りを考え、家具や建具を選択する部分の開発が多く、末広がりの構造になるかと思います。とはいえ、共通の市販品の建具ばかり使っていると差別化ができなくなるため、外観を一部変えることもあるかもしれません。
また、家や間取りといったトップのレイヤーでの概念は見た目に影響しない概念を用いることが多いことがわかります。
今後は、親コンポーネント、子コンポーネント、稀に孫コンポーネントという表現を使います。上の図で、風呂コンポーネントの親は家コンポーネント、子はドア2コンポーネントと窓コンポーネントです。照明1、ドア1コンポーネントは、家コンポーネントの孫コンポーネントです。
コンポーネント同士の情報のやり取り(通信)
コンポーネント同士の情報のやり取りを考えます。簡単な例として、円をドルに計算するアプリを考えます。
子コンポーネント1はユーザから数字を受け取り、親コンポーネントは子1から数字を受け取り、加工して子コンポーネント2に渡し、子2は受け取った数字を表示する構成としました。
親から子へのデータの伝達は、propsで実現していました。子から親へのデータの伝達も、propsで実現できます。ただし、親から子へデータを渡す際は、propsに値そのものを入れて渡していましたが、子から親へのデータ伝送では、親が、親が持つstate変数を書き換える関数を、propsで子に渡します。イメージとしては、「この袋の中に、あなたの好きなタイミングで好きな値を入れてね、袋の中は常に監視して、値が入っていたら取り出すから」という感じです。
index.html
テンプレートです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>test</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js
親コンポーネントだけ呼び出しています。
import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import AppParent from './AppParent'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<StrictMode>
<AppParent/>
</StrictMode>
AppParent.js
親コンポーネントは入力用のInputChild、出力用のOutputChildの2つの子を持ちます。InputChildに、値をセットしてもらう関数をpropsとして渡す際に別名がつけられること、関数を渡す際には、(引数)などがなく、関数名だけ渡していることに注意してください。
import { useState } from "react"
import InputChild from "./InputChild"
import OutputChild from "./OutputChild"
const App = () =>{
const [value,setValue] = useState(0)
return(
<p>
<InputChild message = {"ドル"} handler_input = {setValue}/>
<OutputChild message = {"円"} value = {value * 140}/>
</p>
)
}
export default App
InputChild.js
InputChildは、何を入力するかをprops.messageとして親から受け取りp要素に表示します。input要素に変更があれば、イベントハンドラのhandle_onChange関数を起動し、親から渡された親のStateを変更する関数props.handler_inputに、inputの値をセットします。
const InputChild = (props)=>{
const handle_onChange =(e)=>{
props.handler_input(e.target.value)
}
return(
<>
<p>{props.message}を入力してください。</p>
<input type="number" onChange={handle_onChange}/>
</>
)
}
export default InputChild
OutputChild.js
単に親から渡されたpropsを表示しているだけです。
const OutputChild = (props)=>{
return(
<p>{props.message}:{props.value}</p>
)
}
export default OutputChild