Reactは、ユーザーインターフェース構築の為のJavaScriptライブラリです
ライブラリなのでHTMLの一部に利用するなど、小さくはじめることもできます
コンポーネント(スタイル定義と状態管理をまとめたコンポーネント(タグ)を組み合わせる)を作成し、
コンポーネントを組み合わせることで画面全体を作ります
- 目次
JSX
JSXはReact.createElementのシンタックスシュガー(構文を別の簡単な記法で書けるようにしたもの)です
HTMLのようですがJavaScript式(値を生成し、変数に代入できるもの)です
最終的にはオブジェクト(React要素)を生成します
//JSX
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
//React.createElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
- 属性のclassはclassNameにします
- 属性(イベントなど)はキャメルケースになります
- JSXのなかで変数や関数を参照する場合
{}
で囲います
*デフォルトで、JSX に埋め込まれた値はレンダー前にエスケープされます - JSXには1つの親要素だけしか含められません
*Fragmentは子要素をまとめるために、不要なdivなどを追加せずに、唯一の親要素として機能します<>…</>
で利用できます
Reactだけで構築されたアプリケーションでは、ReactDOMによって管理されるルートDOMノード(<div id="root"></>
)を通常は1つだけ持ちます
*Reactを含める場合は、複数のルートDOMノードも可能
React要素をルートDOMノードにレンダーするには、ReactDOM.createRoot()
に DOM要素を渡しroot.render()
に React要素を渡します
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
JSXはコンパイルが必要ですが、手軽に試してみるなら
<script>タグの中でJSX が使えるようになります
*重くなり、本番環境には向きません
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
</script>
コンポーネント
コンポーネントは自身の出力の中で、他のコンポーネントを使用できます
create-react-app(コマンド一つでReactに必要な環境を構築)のReactプロジェクトは階層の一番上に単一の「Appコンポーネント<App />
」を持っています
npx create-react-app プロジェクトのディレクトリ名
基本的に、コンポーネントは関数で定義します(関数コンポーネント)
props(オブジェクト)を引数に取ります
例:Welcomeコンポーネントの呼び出し時に、props として {name: 'Sara'}
を渡します
*コンポーネント名の先頭は大文字(DOMタグと区別するため)にします
//関数コンポーネントの定義(子)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
//コンポーネント<Welcome name="Sara" />(親)
const element = <Welcome name="Sara" />;
root.render(element);
propsは読み取り専用です(変更してはいけません)
変更はstateで実現します
関数コンポーネントとクラスコンポーネント
2019年にReact16.8で「React Hooks」が導入されて以降、関数コンポーネントが主流です
「React Hooks」が使われる前は、関数コンポーネントでは状態が持てなかったので、状態を持つコンポーネントはクラスコンポーネントで実装していました
//クラスコンポーネントの定義
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
children
コンポーネントの開始タグと終了タグではさまれた「childrenというprops」を使い、受け取った子要素を出力できます
例えば:Sidebar のような「汎用的な入れ物」をあらわすコンポーネントで利用します<Sidebar>・・・<Sidebar/>
function Sidebar(props) {
return (
<div>
{props.children}
</div>
);
}
//
function Welcome() {
return (
<Sidebar>
<h1 className="Sidebar-title">
Welcome
</h1>
<p className="Sidebar-message">
message
</p>
</Sidebar>
);
}
コンポーネントを条件的にレンダーする
//if 文を使用
if (trueなら) {
return <Hellow />;
}
//条件部分がtrueであれば&&の後に書かれた要素を表示
{trueなら && <Hellow />}
//true?の後に書かれた要素を表示 falseなら:の後に書かれた要素を表示
{trueなら ? <Hellow /> : ''}
配列をリストにしてコンポーネントの内部でレンダリングする
map() 関数を使い、リスト項目にkey(更新時にkeyを利用して効率的に差分を見つけたため)を割り当ててます
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
//map()の結果をインライン化した場合
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers} />);
Stateとライフサイクル
ライフサイクル
ReactのコンポーネントがDOMツリーにロードされてからDOMツリーから削除される過程で、大きく分けて3つのフェーズがあります
- Mounting:マウント(DOMの初回ロード)された時
- Updating:コンポーネントが更新された時
- Unmounting : アンマウント(DOMから削除)された時
それぞれフェーズに処理を差し込むことのできるライフサイクルメソッドがあります
クラスコンポーネントでのstateの更新とライフサイクルメソッド
ローカルなstateを追加する
stateの変更は、setState() を使用します
setState() を呼び出した場合、与えられたオブジェクトを現在のstateにマージします
メソッドを宣言することで、コンポーネントがマウントしたりアンマウントしたりした際にコードを実行することができます
class Clock extends React.Component {
//クラスのコンポーネントはpropsを引数として親クラスのコンストラクタを呼び出す
constructor(props) {
super(props);
//ローカルなstate
this.state = {date: new Date()};
}
//マウント時
componentDidMount() {
}
//アップデート時
componentDidUpdate() {
}
//アンマウント時
componentWillUnmount() {
//クリーンアップ(コンポーネントが占有していたリソースを解放する)が必要な場合があります
//例えば:removeEventListenerを使ってイベント登録を解除するなど
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
StateとProps
更新するたびに、ページ全体を書き換える(DOMを作り直す)のではなく、
Reactでは、仮想DOM(ツリーがメモリ上に構築さる)が構築されます
propsやstateの変更前と後を比較し、その差をチェック(差分検出処理)して、必要であれば新たな仮想DOMを構築(レンダリング)し、レンダリングの結果を画面に表示(描画)します(DOM操作を必要最小限にします)
全てのデータは、単一方向データフロー(上から下へ)で、子コンポーネントにのみ影響するようにします
stateを所有するコンポーネント以外からはそのstateにアクセスすることができませんが、子コンポーネントにpropsとして自身のstateを渡すことができます
*stateは共通の親コンポーネントか、その階層構造でさらに上位の別のコンポーネントがstateを持つべきです
StateとPropsはコンポーネントが持つ状態(データ)です
コンポーネントの状態が State
親から渡ってくる状態が Props
*Propsは読み取り専用です
- 上位コンポーネントでなるべく状態を持ち、末端のコンポーネントはなるべく状態をもたないようにする
- propsやstateから派生的に作りだす事のできるデータは、stateに保持すべきではありません
- propsとして渡せるものに制限はありません(コンポーネントも渡せます)
stateにはしないデータ
- 親からpropsを通じて与えられたデータ
- 時間経過で変化しないデータ
- コンポーネント内にある他のpropsやstateを使って算出可能なデータ
Reactでのイベント処理
React のイベント「SyntheticEvent(合成イベント)」は、stopPropagation() と preventDefault() を含む、ブラウザのネイティブイベントと同じインターフェイスを持ちつつ、ブラウザ間で同じ挙動をするようになっています
イベント名はキャメルケース(例:onSubmit)です
イベントハンドラには関数を渡します
function Form() {
function handleSubmit(e) {
//デフォルトの動作を抑止するにはpreventDefault()
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
React Hooks
Hooksは関数コンポーネントに「stateやライフサイクル」を 「 フックする(引っ掛ける)」(コンポーネントのレンダリングの間に処理を差し込む) ための関数です
クラスは使いません
Hooksのルール
- フックを呼び出すのはトップレベルのみ(ループや条件分岐、ネストされた関数内で呼び出さない、フックの内部に入れます)
- フックを呼び出すのはReactの関数内のみ(JavaScript 関数から呼び出さない)
useState
値(state)と、値を更新するための関数を返します
stateが作成されるのはコンポーネントの初回レンダリング時だけです
次回以降のレンダリング時には、useStateからは既存のstateの現在値を受け取ります
import React, { useState } from 'react';
//コンポーネントの内部でuseStateを使う
const [state変数, 更新するための関数] = useState(初期値);
分割代入を使うことで、state変数に異なる名前をつけることができます
複数のstate変数はそれぞれを個別に更新することができます
import React, { useState } from 'react';
function Example() {
// 変数名・更新するための関数・初期値
const [count, setCount] = useState(0);
return (
<div>
//クリックするとcountに1をたす
<button onClick={() => setCount(count + 1)}>
Click
</button>
</div>
);
}
新しいstateが前のstateに基づいて計算される場合は、setStateに関数を渡します
この関数は前回のstateの値を受け取り、更新された値を返します
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Click
</button>
ユーザがフォームを変更するたびに、ユーザ入力を反映するようなコンポーネント
*フォームをレンダリングしているコンポーネントが、ユーザ入力でフォームで起きることも制御できます
const App = () => {
const [text, setText] = useState("");
return (
<div className="App">
<input
value={text}
onChange={(event) => setText(event.target.value)}
/>
<p>{text}</p>
</div>
);
};
state変数はオブジェクトや配列も保持できます
*注意:state変数の更新はマージではなく必ず古い値が置換されます
ヒント:更新時にはスプレッド構文と併用します
const [state, setState] = useState({});
setState(prevState => {
//オブジェクトの展開
return {…prevState, …updatedValues};
});
useReducer
useStateより複雑な状態管理を行えるフックです
dispatchを下位コンポーネントに渡せます
const [state, dispatch] = useReducer(reducer, initialArg, init);
reducerは、現在のstateとactionを引数で受け取り、変更後の新しいstateを返却する関数です
actionは変更指示書のようなもので、action.typeをチェックすることでtypeによって異なる処理を行えるように設定し、dispatchメソッドを用意します
dispatchはactionを引数に取り、actionを渡すメソッドです
useReducerは、reducerを受け取り、現在のstateをdispatchメソッドとペアにして返します
初期化には、第2引数に初期値(initialState)を渡します
const initialState = {count: 0};
//reducer関数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
//useReducerにreducerと初期値を渡す
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
//dispatchで登録したactionを呼び出します
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
初期値(initialState)作成を遅延させる場合は、init関数を第3引数に渡します
actionのpayloadプロパティを使ってreducerに値を渡すことができます
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
//
case 'reset':
return init(action.payload);
//
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
//
</>
);
}
useEffect
useEffectはライフサイクルメソッド(componentDidMountとcomponentDidUpdateとcomponentWillUnmount)がまとまったものです
どんな時に必要?
DOMの書き換え、データの読み込み、タイマーなど
useEffect(レンダリング後に実行される関数(コールバック), 依存変数の配列)
state変数を宣言し、副作用を使うことをReactに伝えています
これでReactがコンポーネントのレンダリング時に、副作用を呼び出しDOMが更新されます
*useEffectに渡された関数はレイアウトと描画の後で遅延されたイベントとして実行されます
useLayoutEffect 、関数を呼び出したあと内部での更新を即座に処理します。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
コンポーネントが削除された時に、クリーンアップが必要な場合など
コンポーネントが削除されるタイミングで関数を実行するにはuseEffectのreturn句で関数を返します
useEffect(() => {
// 初回レンダリング時に実行する処理を書く
// comopnentDidMount相当
return () => {
// コンポーネントが削除される時に実行する処理を書く
// componentWillUnmount相当
};
});
特定の値が変わったときだけに副作用を適用するよう、React に伝えるてパフォーマンスを改善できますuseEffect のオプションの第2引数として配列を渡します
1度だけ実行したいという場合、空の配列 []
を第 2 引数として渡します
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); //カウントが変更された場合にのみ再実行
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []); //1度だけ実行したい
useContext
コンテクストは、React コンポーネントのツリーに対して「グローバル(データが、どのネストレベルのコンポーネントからでもアクセスできる)」になります
React.createContext
はコンテクストオブジェクトを作成します
*defaultValue引数は、コンポーネントがツリー内の上位に対応するプロバイダを持っていない場合のみ使用され、コンテクストの値をデフォルト以外で設定するには、ツリーの上の階層<MyContext.Provider>が必要です
import React, {createContext } from 'react';
const MyContext = React.createContext(defaultValue);
コンテクストオブジェクトにはプロバイダ (Provider)コンポーネントが付属しています
プロバイダのvalueプロパティが変更されるたびに再レンダリングされます
<MyContext.Provider value={/* 何らかの値 */}>
useContextは、コンテクストオブジェクト(React.createContext からの戻り値)を受け取り、そのコンテクストの現在値を返します
useContextの引数はコンテクストオブジェクトです
コンテクストの現在値は、ツリー内の最も上位にあるProviderからコンテクストの現在値(valueの値)を取得します
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 現在のテーマ(デフォルトは "light")の為のコンテクストを作成します。
const ThemeContext = React.createContext(themes.light);
// このサンプルでは、"dark" を現在の値として渡しています。
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// この例では、現在のテーマは "dark" です。
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
*useContextを呼び出すコンポーネントはコンテクストの値が変化するたびに毎回再レンダーされます
余談:useContext(MyContext) は
クラスのstatic contextType = MyContext、<MyContext.Consumer>と同等です
useReducerと組み合わせて、グローバルにdispatchを使えるようにできます
useCallbackとuseMemo
パフォーマンスの最適化について
レンダリングのタイミングは、props が変更されたとき、stateが変更されたときで、親コンポーネントがレンダリングされると子コンポーネントも強制的にレンダリングされます
レンダリングコストが高いコンポーネントなどの、再レンダリングを防ぎ、パフォーマンスを最適化する仕組みがあります
useMemo:値のメモ化
レンダリング毎の再計算を避けたい(計算量の多い値を保持する場合など)時に使うようにします、コンポーネントの再レンダリング時に値を再利用できます
依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します
useMemo([コールバック], [依存変数の配列]);
React.memo(高階コンポーネント):コンポーネントのメモ化
propsの変更のみをチェックし再レンダリングの判断をします
*React.memoでラップしているコンポーネントで「useState・useReducer・useContext 」を使っている場合、stateやコンテクストの変化に応じて再レンダリングされます
React.memo(コンポーネント);
useCallback:関数のメモ化
コールバック関数をPropsとして受け取ったコンポーネントはReact.memoを利用しても再レンダリングされます
useCallbackでメモ化したコールバック関数をPropsとして渡すことでレンダリングをスキップできます
useCallback([コールバック], [依存変数の配列]);
カスタムフックス
カスタムフックを作成することで、コンポーネントからロジックだけ分離することができます
何を引数として受け取り、returnで何を返すのか(returnではコンポーネントで利用するデータのオブジェクトまたは配列を返します)を決めて、useで始める関数名をつけた関数を作ります
関数ではHooksの機能を使用できます(ロジック部分をコンポーネントから移す)
カスタムフックス(関数)を別ファイルに作り、利用するコンポーネントファイルにimportして使います