Reactで実験ダッシュボードを作る
これまでHTMLで構造を作り、JavaScriptで動きを加え、Node.jsとExpressでサーバーを作り、データベースに実験データを保存・取得するAPIまで完成させました。しかし画面が複雑になるほど問題が出てきます。遺伝子情報カード、試料リスト、QC結果表、フィルターボタン — これらすべてを1つのHTMLファイルでinnerHTMLで管理すると、コードはすぐに数千行になります。
Reactはこの問題を解決します。自分だけのHTMLタグを作る技術です。<div>や<span>のような基本タグの代わりに、<GeneCard>、<SampleList>、<QcDashboard>のような意味のあるタグを自分で作って使います。実験室で複雑な工程をプロトコル単位に分けて管理するように、ReactはUIをコンポーネント単位に分けて管理します。
コンポーネント:自分だけのタグを作る
コンポーネント(Component)はReactの核心です。関数1つがタグ1つになります:
function GeneCard() {
return (
<div>
<h3>TP53</h3>
<p>Chromosome: 17p13.1</p>
<p>Function: tumor suppressor</p>
</div>
);
}この関数を作ると<GeneCard />というタグとして使えます。必ず大文字で始めなければなりません — Reactは小文字で始まると通常のHTMLタグ、大文字で始まるとユーザーが作ったコンポーネントと区別します。
複数のコンポーネントを組み合わせて画面全体を構成します:
function App() {
return (
<div>
<Header />
<GeneCard />
<SampleList />
</div>
);
}App関数1つを見るだけで、この画面がHeader、GeneCard、SampleListで構成されていることが1秒で把握できます。複雑なHTMLが意味のある名前の背後に隠されています。
コンポーネントの3つのメリット:
- 可読性:コードの構造が一目でわかる
- 再利用性:
<GeneCard />が10箇所で必要なら10回使えばよい - 保守性:カードのデザインを変えるには
GeneCard関数1箇所だけ修正すれば全カードが同時に更新
Props:コンポーネントにデータを渡す
上のGeneCardは常にTP53だけ表示します。他の遺伝子も表示するにはProps(Propertiesの略)を使います。関数の引数と同じ概念です:
function GeneCard(props) {
return (
<div>
<h3>{props.name}</h3>
<p>Chromosome: {props.chromosome}</p>
<p>Function: {props.geneFunction}</p>
</div>
);
}
function App() {
return (
<div>
<GeneCard name="TP53" chromosome="17p13.1" geneFunction="tumor suppressor" />
<GeneCard name="BRCA1" chromosome="17q21.31" geneFunction="DNA repair" />
<GeneCard name="EGFR" chromosome="7p11.2" geneFunction="receptor tyrosine kinase" />
</div>
);
}<GeneCard name="TP53" chromosome="17p13.1" />のname、chromosome、geneFunctionがPropsです。Reactがこれらの値を1つのオブジェクトにまとめて関数の最初の引数(props)として渡します。
JSXで波括弧{}は「ここはJavaScript領域」という印です。{props.name}と書けばpropsオブジェクト内のnameの値が出力されます。波括弧なしでprops.nameと書くと、その文字がそのまま画面に表示されます。
実験プロトコルで例えると — GeneCardはプロトコルのテンプレートで、Propsはそのテンプレートに入れるサンプルです。同じプロトコルでもサンプル(データ)によって結果が変わります。
リストレンダリング:配列データをコンポーネントに
試料が3個ではなく100個なら<GeneCard />を100回書くわけにはいきません。配列データをループで処理します:
function SampleList(props) {
const items = [];
for (let i = 0; i < props.samples.length; i++) {
const sample = props.samples[i];
items.push(
<li key={sample.id}>
{sample.id}: OD={sample.od} — {sample.status}
</li>
);
}
return (
<ul>{items}</ul>
);
}
function App() {
const sampleData = [
{ id: "S001", od: 1.85, status: "pass" },
{ id: "S002", od: 0.42, status: "fail" },
{ id: "S003", od: 2.10, status: "pass" }
];
return (
<div>
<h2>試料QC結果</h2>
<SampleList samples={sampleData} />
</div>
);
}いくつかの注意点:
key={sample.id}— ループで作った要素には一意なkeyをつける必要があります。Reactがどの項目が変わったかを追跡するために使います。実験室で試料ごとに固有番号をつけるのと同じです。samples={sampleData}— Propsとして配列を渡す時は波括弧で囲みます。文字列はクォーテーション、JavaScript値(配列、数値、変数)は波括弧です。
State:変化するデータの管理
Propsは外部から受け取った値なのでコンポーネント内で変更できません。実験条件書に記載された値を途中で勝手に変えてはいけないのと同じです。しかし実験の進行状態は常に変わります — 「待機中」→「進行中」→「完了」。この内部状態を管理するのがStateです。
function ExperimentTracker() {
const [status, setStatus] = React.useState("待機中");
const [count, setCount] = React.useState(0);
function handleStart() {
setStatus("進行中");
setCount(count + 1);
}
function handleComplete() {
setStatus("完了");
}
return (
<div>
<p>実験状態: {status}</p>
<p>実行回数: {count}</p>
<button onClick={handleStart}>実験開始</button>
<button onClick={handleComplete}>実験完了</button>
</div>
);
}React.useState("待機中")は2つのものを返します:
status— 現在の値(「待機中」)setStatus— 値を変更する関数
setStatus("進行中")を呼ぶとstatusが変わり、Reactが自動で画面を再描画します。innerHTMLでDOMを直接操作する必要はありません — Stateを変えるだけで画面が自動更新されます。これがReactの最大の利点です。
| Props | State | |
|---|---|---|
| 方向 | 外部から入ってくる | 内部で管理 |
| 変更可能 | 不可(読み取り専用) | 可能(setState) |
| 例え | プロトコルに記載された実験条件 | 実験の進行状態 |
| 例 | 遺伝子名、染色体位置 | 「待機中」→「進行中」→「完了」 |
総合例:QCダッシュボード
コンポーネント、Props、Stateを組み合わせると簡単なQCダッシュボードが作れます:
function QcResult(props) {
const style = {
color: props.passed ? "green" : "red",
fontWeight: "bold"
};
return (
<tr>
<td>{props.sampleId}</td>
<td>{props.od}</td>
<td style={style}>{props.passed ? "PASS" : "FAIL"}</td>
</tr>
);
}
function QcDashboard() {
const samples = [
{ id: "S001", od: 1.85, passed: true },
{ id: "S002", od: 0.42, passed: false },
{ id: "S003", od: 2.10, passed: true },
{ id: "S004", od: 0.15, passed: false }
];
const passCount = samples.filter(function(s) { return s.passed; }).length;
return (
<div>
<h2>QC Dashboard</h2>
<p>合格: {passCount} / {samples.length}</p>
<table>
<thead>
<tr><th>試料ID</th><th>OD</th><th>判定</th></tr>
</thead>
<tbody>
{samples.map(function(s) {
return <QcResult key={s.id} sampleId={s.id} od={s.od} passed={s.passed} />;
})}
</tbody>
</table>
</div>
);
}QcResultは試料1件の結果を表示するコンポーネント、QcDashboardは全体をまとめるコンポーネントです。データが100件に増えてもQcResultコンポーネントは1行も変える必要がありません — データを追加するだけです。
やってみよう(Faded Example)
空欄を埋めて遺伝子情報を表示するReactコンポーネントを完成させてください。
function GeneInfo() {return (<div><h3>{props.}</h3><p>Length: {.length_bp} bp</p></div>);}// 使い方: <GeneInfo name="TP53" length_bp={19149} />
よくあるエラーと解決法
Q: コンポーネントが画面に表示されません
コンポーネント名が小文字で始まるとReactは通常のHTMLタグと認識します。geneCard → GeneCardのように先頭を大文字に変えてください。
Q: Each child in a list should have a unique "key" propの警告
ループで作った要素にkey propがありません。配列の各項目が持つ固有の値(ID、試料番号など)をkeyに指定してください。インデックス(i)をkeyにするのは推奨されません。
Q: 波括弧とクォーテーションの使い分けがわかりません
文字列はクォーテーション:name="TP53"。数値、配列、変数などJavaScript値は波括弧:count={42}、data={myArray}。JSX内でJavaScriptコードを実行するには波括弧で囲みます:{props.name}。
Q: Stateを直接変更したのに画面が更新されません
status = "完了"のように直接値を変えるとReactは変更を検知できません。必ずsetStatus("完了")のようにsetter関数を使ってください。そうしないとReactが画面を再描画しません。