React コンポーネントで身につける宣言的な UI |研修コースに参加してみた
今回参加したコースは React コンポーネントで身につける宣言的な UI です。
これまでも SE カレッジでは React に関連するコースを開催してきましたが、多くはチュートリアルに近いものでした。 今回は React を現場で使うのに必要となる コンポーネント を学び、よく言われる「 React で宣言的に UI を書く」ことをやってみます。
とはいえ、この「宣言的に」というのがポイントで、ともすると、これまでの jQuery のように DOM 操作したほうが、わかりやすいし、速いよ、と思ってしまいます。 私もコース受講前だけでなく、受講中もそう思っていました。 しかし、コードを実際書いてみると「宣言的に書くほうがいいじゃん!」という気持ちになりました。
では、どのような内容だったのか、レポートします!
もくじ
コース情報
想定している受講者 |
|
---|---|
受講目標 |
|
講師紹介
このコースでは、プログラミング分野での登壇が多く演習設計が上手な 冨原 祐 さんが登壇されました。
「講師を一生の仕事にする」
3 時間で学ぶ TypeScript 入門 ~ 「型」初心者が挑戦|研修コースに参加してみた
React / Express で作る Web アプリケーション開発入門|研修コースに参加してみた
早速 React の誕生と特徴から振り返り、本題のコンポーネントの説明に入ります。 なお、このコースでは以前に別のレポートでも触れた create-react-app で開発環境を作りました。 create-react-app について詳しくは以下のレポートをご覧ください。
3 時間で React 入門 ~ GraphQL と Relay で作る Web アプリケーション~|研修コースに参加してみた
React コンポーネントとは?
まずは React コンポーネントの説明です。
- React は UI を小さなコンポーネントに分けて管理する
- はじめに大きく分けてから、さらにその分けたものを、あとで細かくわける
- ex. ヘッダ、右ペイン … -> ヘッダをさらに分解
- 修正や追加がしやすくなる
- はじめに大きく分けてから、さらにその分けたものを、あとで細かくわける
- データは親コンポーネントから子コンポーネントに流れる( 単方向データフロー )
- その逆は発生しない
- コンポーネントのつくり方
- シンプルにつくる = クラスを使わず、関数コンポーネントを使う
- ただし大規模に使うときはクラスにまとめる
- 関数コンポーネント or クラスコンポーネントのどちらを使うか、はじめに決める
- シンプルにつくる = クラスを使わず、関数コンポーネントを使う
単方向データフローと聞いて「これは関数型の話! わかりにくくなるかも … 」という直感が働きます。 素朴に変数に代入するので OK な規模感でしか書いたことがないので、ちょっと身が引き締まります。
クラスコンポーネント
クラスコンポーネントの例は次のとおりです。
import React from 'react';
class Greeting extends React.component {
compoentDidMount() {
// ここにコンポーネント生成時の処理を記述
// 正確には配置されるとき。コンストラクタとは別
}
// オーバーライド
render() {
return <h1>Hello, {this.prpos.name}!</h1>;
}
}
// 外部からの呼び出し
export default Greeting;
Greeting というクラスをコンポーネントとして作り、描画時にはこの render() メソッドが呼ばれます。
関数コンポーネント
関数コンポーネントの例は次のとおりです。
import React from 'react';
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
export default Greeting;
Greeting という関数をコンポーネントとして作り、この関数の戻り値が描画されます。
関数コンポーネントは、とてもシンプルですね。 小規模での開発によく使うというのも頷けます。
props
親コンポーネントから子コンポーネントへのデータの受け渡しには props というオブジェクトを使います。 この props は読み取り専用のオブジェクトです。
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
関数型の考え方らしい、値をイミュータブルに保つ仕組みですね。
React コンポーネントの単方向データフローを支える仕組み
コールバック
繰り返しになりますが、 React のデータフローは親コンポーネントから子コンポーネントしかない単方向データフローです。 ただ、そのままでは子コンポーネント側でデータ変更が発生したときに画面全体に反映することができません。 そのために、 コールバック を使います。
- コールバック関数 を使う
- 親コンポーネントから子コンポーネントにコールバック関数を渡す
- 子コンポーネントは、そのコールバック関数を実行することで親コンポーネントの処理を動かす
コールバックの例を見てみます。
- 子の onClick で handleClick が動く
- 親の ChildComponent onClick にある handleChildClick が動く
- コールバック関数で引数を渡すことも可能
function ParentComponent() {
const handleChildClick = () => {
// 子コンポーネントからトリガーされる処理
};
// 子コンポーネントを内包した JSX を返す
return <ChildComponent onClick={handleChildClick} />;
}
function ChildComponent(props) {
const handleClick = () => {
props.onClick(); // 親コンポーネントのコールバック関数を実行
};
// ボタンを内包した JSX を返す
return <button onClick={handleClick}>Click me</button>;
}
再帰的な記述で、ちょっとややこしい印象を受けます。 jQuery でええやん … とボソッとつぶやきたくなりました。
クラスコンポーネントで state 管理
単方向データフローなので、状態の管理( state )や画面遷移( ルーティング )の機能は React 本体に含まれていません。 こうした機能は、他のライブラリなどを利用して実装する必要があります。
コースでは、まず React Router を使った画面遷移( ルーティング )を解説いただきましたが、このレポートでは割愛して、状態の管理 state だけに触れます。
state とはコンポーネントの内部で変更される値を管理するものです。
- コンポーネント内部の状態を key, value で管理
- 具体的には
render()
の状態
- 具体的には
- 初期化するのにコンストラクタを使う(関数コンポーネントでも使える)
- 値のセットに
this.setState()
を使う - 値のアクセスは
this.state
カウンターの値を持つ Counter コンポーネントを例にした実装を見てみましょう。
クラスコンポーネント code
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<h1>Count: {this.state.count} (class component)</h1>
<button onClick={this.handleIncrement}>Increment</button>
</div>
);
}
}
React Hooks で state 管理
先程はクラスコンポーネントの例でしたが、 React Hooks が登場し、関数コンポーネントでも state を利用できるようになりました。 具体的には useState フックを使います
- useState() の 2 つの引数
- 第 1 引数は、状態の初期値を設定するための値
- 第 2 引数は、状態を更新するための関数
Counter の例と同じものを関数コンポーネントで実装します。
関数コンポーネント code
function Counter() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount(count + 1);
}
return (
<div>
<p>Count: {count} (function component)</p>
<button onClick={handleIncrement}>Click me</button>
</div>
);
}
実行してみましょう。
… とはいえ、聞いていると、イミュータブルにするのが理想とはいえ、現実にはコールバックや useState などを使っていて、うーむ、これでは DOM に値を入れて管理するほうがラクチンなのでは、という気持ちになりました。
そこで冨原さんにコース内で質問してみると、
format_quote
という回答をもらいました。 なるほど! React は学習コストが高いとはいえ、今までの不便さ(これは私の単なる経験不足から感じてない)に比べると、わかりよく管理しやすいということなのですね。
関数コンポーネントで Todo アプリを作る
ここまでの内容をふまえて、簡単で定番の Todo アプリを作ってみます。 コースでは関数コンポーネントとクラスコンポーネントで実装しましたが、このレポートでは関数コンポーネントのみを取り上げます。
- Todo アプリの機能
- タスク一覧表示
- タスクの追加
- タスクの削除
- 画面更新はしない
なお、今回は App.js というファイルに関数のすべてをまとめます。
コンポーネントを考える
まず、登場するコンポーネントと、その作り方を考えます。
- タスク一覧と新規追加タスクの 2 つのコンポーネントが必要
- タスク一覧は state を持ったほうが良さそう
- 新規追加タスクは state を持ったほうが良さそう
今回は以下のように作成します。
const [tasks, setTasks] = useState([]); // tasks は配列
const [newTask, setNewTask] = useState(""); // newTask は文字列
タスク一覧を表示
まずはタスク一覧を作成します。
return (
<div>
<h1>やること一覧(関数コンポーネント)</h1>
<ul>
{tasks.map((task, index) => (
<li key={index}>
{task}
</li>
))}
</ul>
{}
は、 JSX の先頭に {}
を使うため、 ()
になる動作確認するために、初期値を設定して確認します。
const [tasks, setTasks] = useState(["タスク1", "タスク2"]);
input ボックスを作成
次に、タスクを追加するための input ボックスを作成します。
- onChange で変更があれば event (input ボックスそのもの) の value を変更し保持する
- JavaScript の onChange と違い、 React の onChange では 1 文字変更があるたびに動作
- 画面の状態と value の状態を常に一致させることが目的
- useState() で作った更新用の setNewTask() に入力値の value = newTask を渡す
- onClick で Add ボタンが押されたら handleAddTask を動かす
onClick={}
の記述は JSX 特有- handleAddTask は value を保存する処理ではなく、 useState() を更新する
- 仮に useState() が複数の状態を管理していれば、それも更新できる(便利!)
<div>
<input
type="text"
value={newTask}
onChange={(event) => setNewTask(event.target.value)}
/>
<button onClick={handleAddTask}>Add</button>
</div>
useState の威力を感じますね。
Add ボタンのクリック時の処理
次に Add ボタンクリック時の handleAddTask を作成します。
const handleAddTask = () => {
setTasks([...tasks, newTask]);
setNewTask("");
};
useState の威力を感じますね( 2 回目)! 画面再描画の記述( ex. getElementyId()
や $('#id')
のような記述)が要らないとは。 変数や DOM の値を更新するのではなく、宣言した state を変える、という感覚がなんとなくつかめます。
Delete ボタンを設置
最後に Delete ボタンを押してタスクを削除する処理です。 タスクの隣にボタンを配置します。
return (
<ul>
{tasks.map((task, index) => (
<li key={index}>
{task}
<button onClick={() => handleDeleteTask(index)}>Delete</button>
</li>
))}
</ul>
最後に、 handleDeleteTask() の処理を書きます。
const handleDeleteTask = (index) => {
const newTasks = [...tasks];
newTasks.splice(index, 1);
setTasks(newTasks);
};
[...tasks]
で tasks のすべての値を指す
- ドット 3 つ
...
は JavaScript のスプレッド構文
これも setTasks の状態を newTasks に変えるだけ、という処理です。 そして、これで完成です。
実行すると次のようになります。
全体を通じて、描画に関する処理( DOM 操作)がまったくないのが衝撃で、そしてラクすぎる! 前半の講義パートで DOM 操作で十分なのでは? と質問してしまって、ごめんなさい 🙇♂️🙇♂️🙇♂️ 。
このあと、同じ Todo アプリをクラスコンポーネントで作りましたが、イベントハンドラを入れて、 setState() で値を更新するところがポイントでした。
React Hooks 一覧
さらに詳しく React の機能を掘り下げるパートとして、 React Hooks の一覧を紹介いただきました。
現場では今回触れた useState と、 useEffect 、 useContext をよく使うとのことでした。 その中でも useContext は props で順次、親 -> 子へデータを渡すのではなく、 Context にデータを入れて、それを受け取るものでした。
大量のデータをもつような画面ではとても重宝しそうですね。
最後に、受講後にも取り組める課題として「じゃんけんゲーム」が用意されていました。 コースではその仕様と動作を紹介し、実装に必要な補足説明をいただき、取り組んだところでコースは修了しました。
まとめ
React コンポーネントにおけるデータ操作、特に単方向データフローについて、 props やコールバック、 React Hooks などを、解説と実践例とともに学びました。
字面での解説では単方向データフローの良さを理解できず、なぜコールバックや React Hooks を使わねばならないのかと感じたのですが、実際にコードを書いてみると、 DOM 操作がなく、描画とデータが切り離されている威力を、まざまざと見せつけられました。 React は宣言的に UI の状態を書くと言われますが、状態だけを書けばよい、という便利さがわかりました!
React というと JSX やこの状態を管理するという関数型のお作法が頭に入りにくいのですが、このコースでその良さを体験できました! React を使うことに抵抗感がある方にはとてもオススメです!
label SEカレッジを詳しく知りたいという方はこちらから !!
IT専門の定額制研修 月額28,000円 ~/ 1社 で IT研修 制度を導入できます。
年間 900 コースをほぼ毎日開催中!!
SEプラスにしかないコンテンツや、研修サービスの運営情報を発信しています。