はじめに
本記事は、React未経験者がReactで実装しながらReactをサラッと知ることを目的としています。
掲載されているソースコードを拡張子html(文字コードはutf-8)で保存してChromeにドラッグすれば動作を確認できます。
※開発環境の構築は一切不要です。
※1章~5章はReactの基本的な説明、6章~10章でサンプルアプリを開発します。
※本記事では開発環境の構築の手間を省くため必要なライブリはCDNからダウンロードする方式を採用しています。
※業務でReactを採用する場合はビルド環境を構築しましょう。アプリの特性や開発規模、Reactの最新の動向などからベストプラクティスを探りながら実装してください。
1 Hello World!
早速ですが、定番の「Hello World!」をReactを使ってブラウザに描画したいと思います。
1.1 ソースコード
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script>
ReactDOM.render(
React.createElement('div', null, 'Hello World!'), // React.createElementは第1引数に与えられたタイプのReact要素を作成します。
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
</body>
</html>
1.2 プログラムの解説
まずReactを使用するためのプログラムを読み込んでいます。
<script>~</script>に書かれているコードが、ブラウザ画面に「Hello World!」を描画しています。
> ```Javascript
<script>
ReactDOM.render(
React.createElement('div', null, 'Hello World!'), // React.createElementは第1引数に与えられたタイプのReact要素を作成します。
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
ReactDOM.renderはブラウザに描画する命令で、二つのパラメータを与えることができます。ひとつは「何を」で、もうひとつは「どこに」です。
「何を」にはReact.createElementの戻り値を渡しています。React.createElementはReact要素を作るための命令です。「どこに」はdocument.getElementByIdの戻り値を渡しています。
その結果、<div id="app">の所に<div>Hello World!</div>が描画されました。
2 JSXによる実装
ソースコードをJSX構文を使った書き方に改良します。
2.1 ソースコード
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
<div>Hello World!</div>,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
</body>
</html>
2.2 プログラムの解説
ReactDOM.render関数の引数に突然HTMLのタグが書かれていると思います。
前回のコードと比べてみましょう。
(前回)React.createElement('div', null, 'Hello World!')
(今回)<div>Hello World!</div>
どうでしょうか。今回のコードの方がどのような表示結果になるか直感的に分かると思います。これがJSX構文です。
実を言うとこのJSX構文、ブラウザには理解できない命令(書き方)なんです。
よってこのコードだとプログラムが動かないので、ブラウザが理解できるコードに変換する必要があります。
そこでJSX構文をReact.createElement命令に変換するためのプログラムを読み込んでいます。
さらに「type="text/babel"」を追記することで、この<script type="text/babel">~</script>の間に書かれたJSX構文がReact.createElement命令に変換されて「Hellow World!」が表示されています。
> ```Javascript
<script type="text/babel">
3 クラスコンポーネント
「Hellow World!」を表示するコードを再利用可能なUIの部品であるクラスコンポーネントへと修正します。
3.1 ソースコード
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
// クラスコンポーネントを定義
class Hello extends React.Component {
render() {
return <div>Hello World!</div>;
}
}
ReactDOM.render(
<Hello />,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
</body>
</html>
3.2 プログラムの解説
Helloクラスを定義し、renderメソッドを実装しています。
renderメソッドはJSX構文で書かれた「<div>Hello World!</div>」を戻していますね。
class Hello extends React.Component {
render() {
return <div>Hello World!</div>;
}
}
ReactDOM.renderメソッドの第1引数は定義したHelloクラス名を指定しています。
結果、Helloクラスのrenderメソッドが呼ばれ「Hellow World!」が表示されました。
> ```Javascript
ReactDOM.render(
<Hello />,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
4 state(状態)の導入
クラスコンポーネントにstateを導入します。
4.1 ソースコード
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
// クラスコンポーネントを定義
class Hello extends React.Component {
constructor(props) {
super(props);
this.UpdateButton = this.UpdateButton.bind(this);
this.state = {
msg: (new Date()).toLocaleTimeString(),
};
}
UpdateButton(event) {
this.setState({
msg: (new Date()).toLocaleTimeString()
});
}
render() {
return <div><button onClick={this.UpdateButton}>更新</button><div>{this.state.msg}</div></div>;
}
}
ReactDOM.render(
<Hello />,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
</body>
</html>
4.2 プログラムの解説
クラスにコンストラクターが新たに実装されています。
コンストラクターはクラスがインスタンス化された際に最初に呼ばれる関数です。
そのコンストラクターの中でステートのmsg(state.msg)に現在時刻の文字列を代入しています。
constructor(props) {
super(props);
this.UpdateButton = this.UpdateButton.bind(this);
this.state = {
msg: (new Date()).toLocaleTimeString(),
};
}
続いて、UpdateButtonは更新ボタンが押下されたら呼ばれる関数で、その中でthis.setState関数を呼んでいます。その際に現在時刻の文字列をstate.msg(ステート)に設定し引数として渡しています。
> ```Javascript
UpdateButton(event) {
this.setState({
msg: (new Date()).toLocaleTimeString()
});
}
renderメソッドでは、更新ボタンとstate.msg(ステート)をdivで挟んで表示していますね。
render() {
return <div><button onClick={this.UpdateButton}>更新</button><div>{this.state.msg}</div></div>;
}
このプログラムを見て「更新ボタンを押下した際にどこで新しい現在時刻を再描画しているのか」疑問に思う方がいると思います。その再描画のお仕事はReactが勝手にやります。
this.setState関数を呼んで新しいステートを設定するまでがプログラマーの責任範囲で、再描画の処理(DOMの操作)をコーディングする必要は一切ありません。
# 5 ライフサイクル
クラスコンポーネントのライフサイクルを確認します。
## 5.1 ソースコード
```Javascript:#5.html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
// クラスコンポーネントを定義
class Hello extends React.Component {
constructor(props) {
console.log('constructor');
super(props);
this.UpdateButton = this.UpdateButton.bind(this);
this.state = {
msg: this.props.initialMsg,
};
}
// ComponentがMountされた後に実行されるメソッド
componentDidMount() {
console.log('componentDidMount');
}
// 新しいpropsまたはstateが受け取られると描画する前に呼び出される
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate');
if (this.state.msg == nextState.msg) {
return false; // falseを返すと再描画が抑止される
}
return true;
}
// Componentのpropsまたはstateが変更されたときに実行される
componentDidUpdate(oldProps, oldState) {
console.log('componentDidUpdate');
}
// ComponentがUnmountされるときに実行される
componentWillUnmount() {
console.log('componentWillUnmount');
}
UpdateButton(event) {
this.setState({
msg: (new Date()).toLocaleTimeString()
});
}
render() {
return <div><button onClick={this.UpdateButton}>更新</button><div>{this.state.msg}</div></div>;
}
}
ReactDOM.render(
<Hello initialMsg={'Hello KHI!'} />,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
</script>
</body>
</html>
5.2 プログラムの解説
クラスがインスタンス化される際にinitialMsg={'Hello KHI!'}を、propsを経由してコンストラクターへ渡しています。
そして、コンストラクター内でstateに'Hello World!'を設定しています。
ReactDOM.render(
<Hello initialMsg={'Hello KHI!'} />,
document.getElementById("app") // render(描画)する場所を指定。今回はid=appのdivですね。
);
> ```Javascript
this.state = {
msg: this.props.initialMsg,
};
その他、様々なライフサイクルメソッドが定義されています。
どのような順番でメソッドが呼ばれているか、Chromeのコンソールのログを見て確認してください。
shouldComponentUpdateやcomponentDidUpdateは、stateの内容が変更されると呼び出されるメソッドです。
更新ボタンを押下する度にshouldComponentUpdateとcomponentDidUpdateがログに出力されますね。
6 「ワクワク ワクチン接種アプリ」の開発
ここからは「ワクワク ワクチン接種アプリ」の開発に着手したいと思います。
完成イメージは以下の感じです。
6.1 ソースコード
まずはじめにワクチン接種者の一覧を実装します。
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
var headers = ["名前", "種類", "回目", "接種日"];
var data = [
["〇〇太郎", "ファイザー", "1", "2021/03/01"],
["△△花子", "モデルナ", "1", "2021/03/01"],
["□□次郎", "アストラゼネカ", "1", "2021/03/01"],
];
// クラスコンポーネントを定義
class VaccineList extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.initialData,
};
}
render() {
return (
<table>
<thead>
<tr>
{this.props.headers.map((title, idx) => <th key={idx}>{title}</th>)}
</tr>
</thead>
<tbody>
{
this.state.data.map((row, rowidx) =>
<tr key={rowidx}>
{
row.map((cell, idx) => <td key={idx}>{cell}</td>)
}
</tr>)
}
</tbody>
</table>
)
}
}
ReactDOM.render(
<VaccineList headers={headers} initialData={data} />,
document.getElementById("app")
);
</script>
</body>
</html>
6.2 プログラムの解説
まずアプリで使うデータを定義しています。
var data = [
["〇〇太郎", "ファイザー", "1", "2021/03/01"],
["△△花子", "モデルナ", "1", "2021/03/01"],
["□□次郎", "アストラゼネカ", "1", "2021/03/01"],
];
続いてVaccineList(クラスコンポーネント)ではコンストラクターとrenderの二つメソッドを実装しています。
renderメソッドの中でmapのコールバックを使ってtableを組み立てています。
JSX構文で書いているので慣れてないと読み辛いかもしれません。
<VaccineList headers={headers} initialData={data} />でクラスをインスタンス化してheadersとdataを、propsを経由してVaccineListのコンストラクターへ渡しています。
>```Javascript
ReactDOM.render(
<VaccineList headers={headers} initialData={data} />,
document.getElementById("app")
);
constructor(props) {
super(props);
this.state = {
data: this.props.initialData,
};
}
# 7 Reduxの導入
Reduxを導入しstate(状態)をstoreで一括で管理します。
## 7.1 ソースコード
```Javascript:#7.html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.2/react-redux.min.js"></script>
<script type="text/babel">
//stateの初期値
const initialState = {
headers: ["名前", "種類", "回目", "接種日"],
data: [
["〇〇太郎", "ファイザー", "1", "2021/03/01"],
["△△花子", "モデルナ", "1", "2021/03/01"],
["□□次郎", "アストラゼネカ", "1", "2021/03/01"],
],
};
// クラスコンポーネントを定義
class VaccineList extends React.Component {
constructor(props) {
super(props);
// this.state = {
// data: this.props.initialData,
// };
}
render() {
return (
<table>
<thead>
<tr>
{this.props.headers.map((title, idx) => <th key={idx}>{title}</th>)}
</tr>
</thead>
<tbody>
{
this.props.data.map((row, rowidx) =>
<tr key={rowidx}>
{
row.map((cell, idx) => <td key={idx}>{cell}</td>)
}
</tr>)
}
</tbody>
</table>
)
}
}
// stateを変化させる関数を定義
var reducerVaccine = function(state, action) {
switch (action.type) {
default:
return state;
}
};
// storeを生成
var store = Redux.createStore(reducerVaccine, initialState);
// stateの値をVaccineListのthis.propsにセットする
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
// storeとコンポーネントを結びつける
var connect = ReactRedux.connect;
var VaccineContainer = connect(mapStateToProps)(VaccineList);
var Provider = ReactRedux.Provider;
var containers = (
<Provider store={store}>
<VaccineContainer/>
</Provider>
);
ReactDOM.render(
containers,
document.getElementById("app")
);
</script>
</body>
</html>
7.2 プログラムの解説
これまでstate(状態)は各クラスコンポーネントのstate(状態)として管理していましたが、今回からstoreと呼ばれる状態を管理する専用の箱で一括管理しています。
まず必要なライブラリを読み込んでいます。
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.2/react-redux.min.js"></script>
続いて本アプリで使うデータ(state)の初期値をinitialStateに代入しています。
>```Javascript
const initialState = {
headers: ["名前", "種類", "回目", "接種日"],
data: [
["〇〇太郎", "ファイザー", "1", "2021/03/01"],
["△△花子", "モデルナ", "1", "2021/03/01"],
["□□次郎", "アストラゼネカ", "1", "2021/03/01"],
],
};
クラスコンポーネントでのstateの管理をやめるのでstateへの代入をコメントアウトし、参照もthis.state.dataからthis.props.dataに変更しています。
// this.state = {
// data: this.props.initialData,
// };
>```Javascript
this.props.data.map((row, rowidx) =>
最後にstoreを使うためのステップでstateを変化させる関数を定義したり、storeとクラスコンポーネントを結び付けたりしています。
// stateを変化させる関数を定義
var reducerVaccine = function(state, action) {
switch (action.type) {
default:
return state;
}
};
// storeを生成
var store = Redux.createStore(reducerVaccine, initialState);
// stateの値をVaccineListのthis.propsにセットする
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
// storeとコンポーネントを結びつける
var connect = ReactRedux.connect;
var VaccineContainer = connect(mapStateToProps)(VaccineList);
var Provider = ReactRedux.Provider;
var containers = (
<Provider store={store}>
<VaccineContainer/>
</Provider>
);
# 8 データ登録欄の実装
接種者の登録欄を実装します。
## 8.1 ソースコード
```Javascript:#8.html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.2/react-redux.min.js"></script>
<script type="text/babel">
//stateの初期値
const initialState = {
headers: ["名前", "種類", "回目", "接種日"],
data: [
["〇〇太郎", "ファイザー", "1", "2021/03/01"],
["△△花子", "モデルナ", "1", "2021/03/01"],
["□□次郎", "アストラゼネカ", "1", "2021/03/01"],
],
};
// クラスコンポーネントを定義
class VaccineReg extends React.Component {
constructor(props) {
super(props);
this.txtName = React.createRef();
this.txtType = React.createRef();
this.txtNumber = React.createRef();
this.txtDate = React.createRef();
this.addVaccine = this.addVaccine.bind(this);
}
addVaccine(e) {
let addVaccineAction = {type: "ADD_VACCINE", payload: {
name: this.txtName.current.value,
type: this.txtType.current.value,
number: this.txtNumber.current.value,
date: this.txtDate.current.value,}};
store.dispatch(addVaccineAction);
}
render() {
return (
<div>
<input ref={this.txtName} type="text" style={{width: "80px"}} />
<input ref={this.txtType} type="text" style={{width: "80px"}} />
<input ref={this.txtNumber} type="text" style={{width: "80px"}} />
<input ref={this.txtDate} type="text" style={{width: "80px"}} />
<button onClick={this.addVaccine}>登</button>
</div>
)
}
}
class VaccineList extends React.Component {
constructor(props) {
super(props);
// this.state = {
// data: this.props.initialData,
// };
}
render() {
return (
<div>
<VaccineReg />
<table>
<thead>
<tr>
{this.props.headers.map((title, idx) => <th key={idx}>{title}</th>)}
</tr>
</thead>
<tbody>
{
this.props.data.map((row, rowidx) =>
<tr key={rowidx}>
{
row.map((cell, idx) => <td key={idx}>{cell}</td>)
}
</tr>)
}
</tbody>
</table>
</div>
)
}
}
// stateを変化させる関数を定義
var reducerVaccine = function(state, action) {
switch (action.type) {
case 'ADD_VACCINE':
let newData = state.data.map(line => ([...line]));
newData.push([action.payload.name,
action.payload.type,
action.payload.number,
action.payload.date]);
return {
...state,
data: newData
};
default:
return state;
}
};
// storeを生成
var store = Redux.createStore(reducerVaccine, initialState);
// stateの値をVaccineListのthis.propsにセットする
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
// storeとコンポーネントを結びつける
var connect = ReactRedux.connect;
var VaccineContainer = connect(mapStateToProps)(VaccineList);
var Provider = ReactRedux.Provider;
var containers = (
<Provider store={store}>
<VaccineContainer/>
</Provider>
);
ReactDOM.render(
containers,
document.getElementById("app")
);
</script>
</body>
</html>
8.2 プログラムの解説
新たにVaccineReg(クラスコンポーネント)を実装しています。
VaccineRegにはコンストラクター、addVaccine、renderの三つのメソッドがありますね。
VaccineRegのrenderメソッドを見てください。データ登録欄のinputタグやbuttonが記述されています。
そのbuttonのonClickでthis.addVaccineメソッドを呼んでいるのが分かります。
VaccineRegのaddVaccineメソッドでaddVaccineAction変数にtypeとpayloadを持つオブジェクトを代入しています。
そのpayloadにはテキストボックスに入力された値を詰めています。
そして最後にstore.dispatchを呼んでいます。
VaccineListのrenderメソッドにてtableタグの上部にVaccineRegを組み込んでいます。
<div>
<VaccineReg />
<table>
続いて、先程のstore.dispatchの呼び出しで以下が実行されます。
>```Javascript
case 'ADD_VACCINE':
let newData = state.data.map(line => ([...line]));
newData.push([action.payload.name,
action.payload.type,
action.payload.number,
action.payload.date]);
return {
...state,
data: newData
};
action.typeがADD_VACCINEの場合は、ワクチン接種者の一覧データが入ったstate.dataをディープコピーしてnewDataを生成しています。
※[...line]はスプレッド構文で、line(2次元配列state.dataの横(列)の配列データ)を展開しています。
そしてpayloadで渡ってきた登録対象のデータを取得して、ディープコピーしたnewDataにpushしています。
最後に新たなstateとなるオブジェクトを返しています。但しdataはディープコピーしたnewDataに差し替えています。
return {
...state,
data: newData
};
※Reducerメソッドでは引数で渡ってきたstateの内容を変更してはいけないルールです。変更したい場合はコピーして変更します。
stateが変更されると以下の関数を経由してVaccineListのthis.propsに渡され再描画されます。
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
# 9 ソート機能の実装
ワクチン接種者一覧にソート機能を実装します。
## 9.1 ソースコード
```Javascript:#9.html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.2/react-redux.min.js"></script>
<script type="text/babel">
//stateの初期値
const initialState = {
headers: ["名前", "種類", "回目", "接種日"],
data: [
["〇〇太郎", "ファイザー", "1", "2021/03/03"],
["△△花子", "モデルナ", "2", "2021/03/02"],
["□□次郎", "アストラゼネカ", "3", "2021/03/01"],
],
};
// クラスコンポーネントを定義
class VaccineReg extends React.Component {
constructor(props) {
super(props);
this.txtName = React.createRef();
this.txtType = React.createRef();
this.txtNumber = React.createRef();
this.txtDate = React.createRef();
this.addVaccine = this.addVaccine.bind(this);
}
addVaccine(e) {
let addVaccineAction = {type: "ADD_VACCINE", payload: {
name: this.txtName.current.value,
type: this.txtType.current.value,
number: this.txtNumber.current.value,
date: this.txtDate.current.value,}};
store.dispatch(addVaccineAction);
}
render() {
return (
<div>
<input ref={this.txtName} type="text" style={{width: "80px"}} />
<input ref={this.txtType} type="text" style={{width: "80px"}} />
<input ref={this.txtNumber} type="text" style={{width: "80px"}} />
<input ref={this.txtDate} type="text" style={{width: "80px"}} />
<button onClick={this.addVaccine}>登</button>
</div>
)
}
}
class VaccineList extends React.Component {
constructor(props) {
super(props);
this.sort = this.sort.bind(this);
// this.state = {
// data: this.props.initialData,
// };
}
sort(e) {
let column = e.target.cellIndex;
this.props.sort({column: column});
}
render() {
return (
<div>
<VaccineReg />
<table>
<thead onClick={this.sort}>
<tr>
{this.props.headers.map((title, idx) => <th key={idx}>{title}</th>)}
</tr>
</thead>
<tbody>
{
this.props.data.map((row, rowidx) =>
<tr key={rowidx}>
{
row.map((cell, idx) => <td key={idx}>{cell}</td>)
}
</tr>)
}
</tbody>
</table>
</div>
)
}
}
// stateを変化させる関数を定義
var reducerVaccine = function(state, action) {
switch (action.type) {
case 'ADD_VACCINE':
{
let newData = state.data.map(line => ([...line]));
newData.push([action.payload.name,
action.payload.type,
action.payload.number,
action.payload.date]);
return {
...state,
data: newData
};
}
case 'SORT':
{
let newData = state.data.map(line => ([...line]));
let column = action.payload.column;
newData.sort(function(a, b) {
return (a[column] === b[column] ? 0 : a[column] < b[column] ? -1: 1);
});
return {
...state,
data: newData
};
}
default:
return state;
}
};
// storeを生成
var store = Redux.createStore(reducerVaccine, initialState);
// stateの値をVaccineListのthis.propsにセットする
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
sort: (payload) => {
dispatch({
type: 'SORT',
payload
});
}
};
};
// storeとコンポーネントを結びつける
var connect = ReactRedux.connect;
var VaccineContainer = connect(mapStateToProps, mapDispatchToProps)(VaccineList);
var Provider = ReactRedux.Provider;
var containers = (
<Provider store={store}>
<VaccineContainer/>
</Provider>
);
ReactDOM.render(
containers,
document.getElementById("app")
);
</script>
</body>
</html>
9.2 プログラムの解説
新たにsort関数を実装してtheadがクリックされたら呼び出しています。
sort(e) {
let column = e.target.cellIndex;
this.props.sort({column: column});
}
>```Javascript
<thead onClick={this.sort}>
以下はdataをソートする処理です。
前回実装したcase 'ADD_VACCINE'と処理の内容は殆ど同じで、newData.pushがnewData.sortに変わったくらいです。
case 'SORT':
{
let newData = state.data.map(line => ([...line]));
let column = action.payload.column;
newData.sort(function(a, b) {
return (a[column] === b[column] ? 0 : a[column] < b[column] ? -1: 1);
});
return {
...state,
data: newData
};
}
前回とは異なる方法でactionをstoreに送信(dispatch)しています。
connectの第2引数にmapDispatchToProps関数を渡すことで、VaccineListクラスコンポーネントのpropsにmapDispatchToProps関数の戻り値(オブジェクト)が渡ります。
>```Javascript
const mapDispatchToProps = (dispatch) => {
return {
sort: (payload) => {
dispatch({
type: 'SORT',
payload
});
}
};
};
var VaccineContainer = connect(mapStateToProps, mapDispatchToProps)(VaccineList);
そして、this.props.sortでそのオブジェクト内の関数を呼んでいます。
>```Javascript
this.props.sort({column: column});
10 削除ボタンの実装
最後に削除ボタンを実装します。
10.1 ソースコード
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/react@17.0.1/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17.0.1/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.2/react-redux.min.js"></script>
<script type="text/babel">
//stateの初期値
const initialState = {
headers: ["名前", "種類", "回目", "接種日"],
data: [
["〇〇太郎", "ファイザー", "1", "2021/03/03"],
["△△花子", "モデルナ", "2", "2021/03/02"],
["□□次郎", "アストラゼネカ", "3", "2021/03/01"],
],
};
// クラスコンポーネントを定義
class VaccineReg extends React.Component {
constructor(props) {
super(props);
this.txtName = React.createRef();
this.txtType = React.createRef();
this.txtNumber = React.createRef();
this.txtDate = React.createRef();
this.addVaccine = this.addVaccine.bind(this);
}
addVaccine(e) {
let addVaccineAction = {type: "ADD_VACCINE", payload: {
name: this.txtName.current.value,
type: this.txtType.current.value,
number: this.txtNumber.current.value,
date: this.txtDate.current.value,}};
store.dispatch(addVaccineAction);
}
render() {
return (
<div>
<div style={{color: "aqua", fontSize: "30px"}}>
ワクワク ワクチン接種アプリ
</div>
<input ref={this.txtName} type="text" style={{width: "80px"}} />
<input ref={this.txtType} type="text" style={{width: "80px"}} />
<input ref={this.txtNumber} type="text" style={{width: "80px"}} />
<input ref={this.txtDate} type="text" style={{width: "80px"}} />
<button onClick={this.addVaccine}>登</button>
</div>
)
}
}
class VaccineList extends React.Component {
constructor(props) {
super(props);
this.sort = this.sort.bind(this);
this.delete = this.delete.bind(this);
// this.state = {
// data: this.props.initialData,
// };
}
sort(e) {
let column = e.target.cellIndex;
this.props.sort({column: column});
}
delete(e) {
let row = e.target.dataset.row;
this.props.delete({row: row});
}
render() {
return (
<div>
<VaccineReg />
<table>
<thead onClick={this.sort}>
<tr>
{this.props.headers.map((title, idx) => <th key={idx}>{title}</th>)}<th></th>
</tr>
</thead>
<tbody>
{
this.props.data.map((row, rowidx) =>
<tr key={rowidx}>
{
row.map((cell, idx) => <td key={idx}>{cell}</td>)
}
<td><button data-row={rowidx} onClick={this.delete}>削</button></td>
</tr>)
}
</tbody>
</table>
</div>
)
}
}
// stateを変化させる関数を定義
var reducerVaccine = function(state, action) {
switch (action.type) {
case 'ADD_VACCINE':
{
let newData = state.data.map(line => ([...line]));
newData.push([action.payload.name,
action.payload.type,
action.payload.number,
action.payload.date]);
return {
...state,
data: newData
};
}
case 'SORT':
{
let newData = state.data.map(line => ([...line]));
let column = action.payload.column;
newData.sort(function(a, b) {
return (a[column] === b[column] ? 0 : a[column] < b[column] ? -1: 1);
});
return {
...state,
data: newData
};
}
case 'DEL_VACCINE':
{
let newData = state.data.map(line => ([...line]));
let row = action.payload.row;
newData.splice(row, 1);
return {
...state,
data: newData
};
}
default:
return state;
}
};
// storeを生成
var store = Redux.createStore(reducerVaccine, initialState);
// stateの値をVaccineListのthis.propsにセットする
const mapStateToProps = function(state) {
return {
headers: state.headers,
data: state.data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
sort: (payload) => {
dispatch({
type: 'SORT',
payload
});
},
delete: (payload) => {
dispatch({
type: 'DEL_VACCINE',
payload
});
},
};
};
// storeとコンポーネントを結びつける
var connect = ReactRedux.connect;
var VaccineContainer = connect(mapStateToProps, mapDispatchToProps)(VaccineList);
var Provider = ReactRedux.Provider;
var containers = (
<Provider store={store}>
<VaccineContainer/>
</Provider>
);
ReactDOM.render(
containers,
document.getElementById("app")
);
</script>
</body>
</html>
10.2 プログラムの解説
新たにdelete関数を実装して削除ボタンがクリックされたら呼び出しています。
delete(e) {
let row = e.target.dataset.row;
this.props.delete({row: row});
}
>```Javascript
<td><button data-row={rowidx} onClick={this.delete}>削</button></td>
以下はdataを削除する処理です。
前回実装したcase 'SORT'と処理の内容は殆ど同じで、newData.sortがnewData.spliceに変わったくらいです。
case 'DEL_VACCINE':
{
let newData = state.data.map(line => ([...line]));
let row = action.payload.row;
newData.splice(row, 1);
return {
...state,
data: newData
};
}
mapDispatchToPropsも前回と殆ど同じでtypeに'DEL_VACCINE'を設定してactionをstoreに送信(dispatch)しています。
>```Javascript
delete: (payload) => {
dispatch({
type: 'DEL_VACCINE',
payload
});
},
おわりに
Reactはネットに情報が多く学習し易いですが、古い情報も残っているので注意が必要です。
業務で利用する場合は最新の動向を確認しましょう。
以上です。