フロント初心者がReact開発をするまでの学び
前書き
フロント開発の楽しさに目覚めつつあるAIエンジニアです。
レスポンスの重要性に気付かされることが多いです。
精度そこそこでなるはやのAIシステムを作れる人を目指していきたいと思います。
今回はフロント初心者がReactで開発するまでの学びを残していきます。
Reactの勉強の仕方として公式のチュートリアルが良いということを聞き、突撃しましたが
なるほど、わからん
状態だったため、基本的な知識がないとスムーズに理解できないだろうといった所感です。
私が読んだ本にはそもそもJavaScriptを知らないとReactの理解は厳しいと書いてありました。
Reactチュートリアル挫折経験者として共感できます。
フロントエンドの開発は技術の移り変わりが早いらしいですが、基本的な概念などは覚えておいて損はないと思うので、その部分からまとめていきます。
参考までに私のバックグラウンドですが、
言語 | 使用経験 | 言語に対する感想 |
---|---|---|
Python | ほとんどコレ | Pythonで良いなら全部Pythonで書きたい。 |
R | 学生の時に少し | 関数型プログラミングは何か違和感がある。 |
Java | 学生の時に少し | 型定義とか基本的な書き方を学べて便利。可読性もあると思ってる。 |
このような感じになっています。
個人的に一番馴染みがあるのがPythonなので、この記事でも用語や構文で詰まった部分ではPythonと比較しながら説明していきます。
フロント開発の基本概念(DOMと仮想DOM)
DOM
フロント開発を進めるとDOM(ドム)という言葉が出てきます。
Document Object Modelの略となります。
簡単に説明するとWebページをプログラムで操作する仕組みです。
具体的には以下のような形でWebページ上にテキストを追加することができます。
const paragraph = document.createElement("p"); // 新しいP要素を作成
paragraph.textContent = "新しい文章を追加しました!"; // テキストを設定
document.body.appendChild(paragraph); // ページに追加
裏ではWebページ全体をツリー構造として表現しています。
全体を表現しているので少しの変更でも再描画・再計算のコストが大きくなってしまったり、
コードが肥大化してくるとどこで何の操作をしているのかが理解しにくくなったりします。
仮想DOM
実際のDOMを操作する前に、その「仮想的なコピー」をメモリ上で扱う仕組みを仮想DOMといいます。
Reactでも仮想DOMを使用しています。
以前のDOMとの差分を比較し、必要な部分だけ実際のDOMを更新することにより再描画・再計算のコストを小さくすることができます。
JavaScript関連の用語や構文で詰まった部分
ここからはJavaScript関連の基本的な用語や構文で詰まった部分をまとめていきます。
オブジェクト
まずはオブジェクトという用語についてです。
PythonやJavaをやっているとクラスから作成したインスタンスのことだと思いがちですが、
JavaScriptではキーと値のペア を持つデータ構造のことをオブジェクトと呼びます。
const fruitPrices = { Apple: 200, Banana: 50 };
細かな違いはありますが、Pythonでいう辞書型dictのことです。
変数の種類
次にJavaScriptの変数の種類についてまとめていきます。
そもそもJavaScriptには3種類の変数(const
, let
, var
)があります。
一般的に以下のような説明がされます。
スコープ内で
・同じ変数名が使えるか?という視点の再宣言
・変数を上書きできるか?という視点の再代入
のパターンごとに区分できます。
表にすると
種類 | 再宣言 | 再代入 |
---|---|---|
const | 不可 | 不可 |
let | 不可 | 可 |
var | 可 | 可 |
となります。
ここでconstは再代入はできませんがリストやオブジェクトの中身の変更はできます。
例を使って説明すると、
const person = { name: "太郎", age: 30 };
// 中身は変更できる
person.name = "次郎"; // 名前を変更
person.age = 25; // 年齢を変更
console.log(person); // { name: "次郎", age: 25 }
// 再代入はできない
person = { name: "三郎", age: 40 }; // エラー
再代入はできないけど、中身の変更はできる??
という点で引っかかったのですが、
正確にはメモリの参照先の変更はできないけど、同一メモリ上の内容変更はできるということらしいです。
オブジェクトやリストの作成時にメモリを確保するので、
同じメモリ上のオブジェクトやリスト内の各要素の上書きはできるけど
別のメモリを参照することになるオブジェクトやリストを新規で作成することはできない。
ということです。
なので、再代入不可という表現より再参照不可という表現の方が(一般的な説明ではないですが)正確になります。
まとめ直すと、
種類 | 再宣言 | 再参照 |
---|---|---|
const | 不可 | 不可 |
let | 不可 | 可 |
var | 可 | 可 |
となります。
初めのうちはそれぞれの変数をどのように使うか迷うかもしれませんが、
種類 | 再宣言 | 再参照 | 使う場面 |
---|---|---|---|
const | 不可 | 不可 | 数値や文字列の定数 or リストやオブジェクト |
let | 不可 | 可 | メソッド内で上書きの必要がある数値や文字列の変数 |
var | 可 | 可 | 基本的に使用しない |
というルールで大丈夫だと思います。
アロー構文
次はアロー関数についてです。
関数を=>
という記号を使って書くことができます。
2つの数値を足し合わせる処理は=>
を使わずに書くと
function add(a, b) {
return a + b;
}
console.log(add(3, 5)); // 8
と書くことになりますが、
=>
という記号を使い、
const add = (a, b) => a + b;
console.log(add(3, 5)); // 8
と書くこともできます。
慣れるまでは読みづらいかもしれませんが、短く書くことができます。
そして、とてもよく出てきます。
次に書くmap関数などと組み合わせてよく使われます。
map関数
次はmap関数についてです。
配列の要素を変更した新しい配列を作成する時にmap関数を使います。
const names = ["太郎", "次郎", "三郎"];
const greetings = names.map(name => `こんにちは、${name}!`);
console.log(greetings);
// ["こんにちは、太郎!", "こんにちは、次郎!", "こんにちは、三郎!"]
Pythonではリストの内包表記がありますが、それと似たような感じです。
names = ["太郎", "次郎", "三郎"]
greetings = [f"こんにちは、{name}!" for name in names]
print(greetings)
# ["こんにちは、太郎!", "こんにちは、次郎!", "こんにちは、三郎!"]
React関連の用語や構文で詰まった部分
ここからはReactについてまとめていきます。
個人的にReactも最初は書き方が独特に感じる部分が多くて慣れるまで時間がかかりました。
独特の概念が多い点もつまづきやすい理由だと思います。
それぞれの概念を直観的に理解しながら少しずつ慣れていけると良いと思います。
今までJavaScriptの説明をしてきましたが、実際の開発では変数の型を明記したTypeScriptを使用することの方が多いと思うので、ここからはTypeScriptで書き進めていきます。
コンポーネント
Reactは画面上のUI(ユーザーインターフェース)構築に特化したライブラリです。
画面上のUIとは、クリックや文字入力などのユーザーの行動とそれらに対応した画面表示やアプリのバックエンドの処理などのなんらかの処理のやりとりを意味します。
ReactではUIの機能ごとにコンポーネントと呼ばれる単位で分割し、開発していきます。
Props
コンポーネントの引数のことをPropsと言います。
下の例ではUserというコンポーネントのPropsとしてnameとageを渡しています。
type UserProps = {
name: string;
age: number;
};
const User = ({ name, age }: UserProps) => {
return <p>{name} は {age} 歳です。</p>;
};
// 使用例
<User name="太郎" age={25} />;
// 結果
// <p>太郎 は 25 歳です。</p>
Pythonのクラスのコンストラクタ(__init__
)の引数と似ています。
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age # インスタンス変数として保存
def introduce(self):
return f"{self.name} は {self.age} 歳です。"
# インスタンス作成
user = User(name="太郎", age=25)
print(user.introduce()) # 太郎 は 25 歳です。
フック(Hooks)
Pythonでは、プログラムの流れ(どの処理をどの順で実行するか)を開発者が決め、プログラムもその順に実行されていきます。
しかしReactのようなフロントエンド開発では、ユーザーの操作やUIの状態に応じて実行する処理が変化します。このような動的な処理を管理するために、Reactではフック(Hooks)という特別な関数が使われます。
ここからは代表的なものとして、useStateとuseEffectについてまとめていきます。
StateとuseState
Pythonのインスタンス変数のようにコンポーネントに変数を保持させたい場合、useStateというフックを使います。
増やす
ボタンがクリックされるごとにカウントをプラス1する処理は以下のように書くことができます。
import { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0); // 初期値を0でcountを初期化
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
</div>
);
};
export default Counter;
Pythonで書くと以下のような処理をしています。
class Counter:
def __init__(self):
self.count = 0 # 初期値を0に設定
def increment(self):
self.count += 1
print(f"カウント: {self.count}")
# インスタンスを作成して使用
counter = Counter()
counter.increment() # カウント: 1
counter.increment() # カウント: 2
useEffect
コンポーネントの状況の変化に応じた処理を実行したい場合にuseEffectというフックを使います。
さらっと状況の変化と書きましたが、具体的には以下の場合です。
状況 | 説明 |
---|---|
マウント | コンポーネントが最初に表示されたとき(初回レンダリング時) |
State / Propsの変化 | Stateの更新や親コンポーネントから渡されたPropsが変わったとき |
アンマウント | コンポーネントが削除される(画面から消える)とき |
useEffectの発動条件はフックの書き方によって変えることができます。
1.フックの後が[]
の場合(空の依存配列)
・初回レンダリング時のみ実行
useEffect(() => {
// 初回レンダリング時のみ実行される処理
}, []);
2.フックの後が[xx]
の場合(特定の依存項目)
・初回レンダリングとxx
が変更される度に実行
useEffect(() => {
// 初回レンダリング時と`xx`が変更された時に実行される処理
}, [xx]);
Stateの`count`が変更される度にログ出力する例
Stateのcount
が変更される度にログ出力する場合、以下のように書くことができます。
import { useState, useEffect } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`countが変更されました: ${count}`);
}, [count]); // countが変わるたびに実行される
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
</div>
);
};
変更されているか否かを判定して処理の実行を分岐しています。
Pythonで書くと以下のような処理をしています。
class Counter:
def __init__(self):
self.count = 0 # 初期状態
self.previous_count = None # 前回の状態(useEffectの依存関係に相当)
def increment(self):
self.count += 1 # 状態を変更
self.use_effect() # 状態変更時に実行したい副作用的な処理
def use_effect(self):
# countが変わった時だけ副作用を実行(例えば、コンソールにプリント)
if self.previous_count != self.count:
print(f"countが変更されました: {self.count}")
self.previous_count = self.count # 前回の状態を更新
self.render()
def render(self):
print(f"現在のカウント: {self.count}")
# 使ってみる
counter = Counter()
counter.render()
# 現在のカウント: 0
counter.increment()
# countが変更されました: 1
# 現在のカウント: 1
counter.increment()
# countが変更されました: 2
# 現在のカウント: 2
counter.increment()
# countが変更されました: 3
# 現在のカウント: 3
3.フックの後に指定がない場合(依存配列がない)
・初回レンダリングとState / Propsいずれかの変化の度に実行
useEffect(() => {
// 初回レンダリング時とState / Propsいずれかの変化時に実行される処理
});
4.useEffect内でreturn
がある場合(クリーンアップ関数)
・アンマウント時にreturn
内に書いた処理が実行
useEffect(() => {
// 初回レンダリング時などに実行する処理
return () => {
// コンポーネントがアンマウントされるときに実行される処理(クリーンアップ)
};
}, []);
後書き
初めて聞く用語や概念が多くて難しかったです。
はじめ時 惑いし道は 暗闇も
振り返しとき いとも明るし
参考
・JavaScriptとReact
・React公式のチュートリアル
Discussion