はじめに
今回は、Reactを使って、メモを追加・削除ができる簡単なメモアプリを作成していきます。
挙動は以下のような感じです。
シリーズ
本記事はReact基礎講座のための連載になっています。気になる方のために、前の章は以下です。
React開発で見かける配列処理系のメソッド map , filter について(React基礎講座8) - Qiita
最初の記事は、以下です。
Reactを使ってJSXの内容をレンダリングする(create-react-app)(React基礎講座1) - Qiita
メモアプリとは
仕様は以下のようなシンプルなReactアプリケーションです。
- テキストエリアにテキストを入力して、入力ボタンを押したらその内容が一覧で表示される
- メモの一覧にはそれぞれ、削除ボタンがあり、それを押したらそのアプリが消える
- メモはDBには格納されない
ファイル構成
root/
├ public/
└ src/
└ components/
| └ Form.js
| └ List.js
└ memoApp.js
└ index.js
- index.js
-
React.Component
であるmemoApp
をimportする
-
- MemoApp.js
- stateとして以下の情報を初期設定します
-
memos
: 具体的なメモの情報 -
nextId
: 次に追加するメモのidの情報- stateを持たせるので、クラスコンポーネントで実装します
-
- メモを保存する機能
addMemo
と、削除する機能deleteMemo
を作る- これら2つの機能が
state
を変更させます
- これら2つの機能が
- コンポーネント
Form.js
とList.js
をimportする- これらも今回はクラスコンポーネントで実装しましょう
- stateとして以下の情報を初期設定します
- /components/Form.js
- メモのフォーム部分の見た目を作る
- /components/List.js
- メモ一覧の見た目を作る
コンポーネントの配置
まずは、ファイル構成どおりにコンポーネントを配置していきましょう。
まずは、index.js
import React from "react";
import { render } from "react-dom";
import MemoApp from "./MemoApp";
render(<MemoApp />, document.getElementById("root"));
次に、MemoApp.js
このコンポーネントから、<Form />
と<List />
をimportして呼び出していることが分かります。
import React from "react";
import { render } from "react-dom";
import Form from "./components/Form";
import List from "./components/List";
class MemoApp extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>MemoApp</h2>
<Form />
<List />
</div>
);
}
}
export default MemoApp;
続いて、Form
とList
コンポーネントを作成していきます。
import React from "react";
import { render } from "react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h3>Form</h3>;
}
}
export default Form;
import React from "react";
import { render } from "react-dom";
class List extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h3>List</h3>;
}
}
export default List;
すると、こんな感じで作成できましたかね?これで、下準備というか、コンポーネントの配置は完了です。
MemoAppにStateを定義してListで表示
MemoAppにStateを定義していきます。
前出しましたが、定義するStateは2種類です。
-
memos
: 具体的なメモの情報- 配列の中に連想配列を持たせましょう(keyは、
id
とcontent
にしましょう) - これ、最終的にはテキストボックスからcontentを生成しますが、今回はコンポーネントの見た目確認のため、Stateに定義します
- 配列の中に連想配列を持たせましょう(keyは、
-
nextId
: 次に追加するメモのidの情報
MemoApp
では、<List />
コンポーネント部分にstateのmemos
を渡して、List.js
で表示させるようにしましょう。
List.js
では、props
で、MemoAppにStateとして定義したmemos
を要素全て表示させましょう。
その時に、使用するメソッドは....mapでしたね。
それでは、実装していきましょう。まずは、MemoApp.js
から。
import React from "react";
import { render } from "react-dom";
import Form from "./components/Form";
import List from "./components/List";
class MemoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
memos: [
{ id: 1, content: "one" },
{ id: 2, content: "two" },
{ id: 3, content: "three" },
{ id: 4, content: "four" },
{ id: 5, content: "five" }
],
nextId: 0
};
}
render() {
return (
<div>
<h2>MemoApp</h2>
<Form />
<List memos={this.state.memos} />
</div>
);
}
}
export default MemoApp;
こちらは、State
を定義して、それをState
として<List />
コンポーネントにmemos
として渡します。
List.js
では、MemoApp.js
からState
に乗って渡ってきたmemos
をmap
で要素ごとに処理した結果を変数list
コンポーネントに入れて、その下のrenderメソッドで{list}
として受け取ります。
import React from "react";
import { render } from "react-dom";
class List extends React.Component {
constructor(props) {
super(props);
}
render() {
const list = this.props.memos.map(memo => {
return (
<li>
#{memo.id} - {memo.content}
</li>
);
});
return (
<div>
<h2>List</h2>
{list}
</div>
);
}
}
export default List;
こんな感じですね。見た目は、こんな感じになります。
Form.jsを作成する
今度は、メモを作成するForm
のコンポーネントを作成します。
import React from "react";
import { render } from "react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = { content: "content" };
}
render() {
return (
<div>
<h2>Form</h2>
<input value={this.state.content} />
<input type="submit" value="Add Memo" />
</div>
);
}
}
export default Form;
ただ、このままだと、テキストエリアの値がいつまでも変わらないので、テキストエリア内の値が更新された場合、その変更後の値をテキストエリアに表示させます。テキストエリア内の値のstateが変更されたことを検知するイベントハンドラといえば、onChange
ですね。これは、フォーム内の要素の内容が変更された時に起こるイベントハンドラです。
これをハンドラにして、メソッドhandleChange
を呼び出すような処理を書きましょう。handleChange
関数の引数にevent
をようして、関数内でevent.target.value
と記述すると、イベントの結果更新された値を取得することができます。それを変数content
として格納する。
その変数(content
)を使って、setState
してフォーム内の値(state)を更新します。つまり、handleChange
関数を使って自分自身の値を更新させていることがわかります。
それでは、実装していきましょう。
import React from "react";
import { render } from "react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = { content: "content" };
}
render() {
return (
<div>
<h2>Form</h2>
<input value={this.state.content} onChange={this.handleChange} />
<input type="submit" value="Add Memo" />
</div>
);
}
handleChange = event => {
const content = event.target.value;
this.setState({ content: content });
};
}
export default Form;
次に、submit
した際の挙動を書いていきましょう。input
タグをform
タグで囲って、<input type="submit" value="Add Memo" />
をクリックした際の処理(関数を)form
の開始タグに記述します。その際のハンドラは、onSubmit
ですね。onSubmit
の詳細は、以下の記事を見て見てください。
参考
onSubmit
で検知した際に、処理させる関数名はhandleSubmit
とでもしましょう。今回も引数event
を受け取るようにしましょう。
処理内容は、一旦以下のようにします。
- まずは、submitをクリックした際のデフォルトの挙動をしないようにする
- 次に、アラートにて、現状の
this.state.content
の値を表示させるようにする - アラートで、現状のcontentを表示させたら、現状のテキストエリアの値は何も無いことにします
こんな感じです。では、実装していきましょう。
import React from "react";
import { render } from "react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = { content: "content" };
}
render() {
return (
<div>
<h2>Form</h2>
<form onSubmit={this.hamdleSubmit}>
<input value={this.state.content} onChange={this.handleChange} />
<input type="submit" value="Add Memo" />
</form>
</div>
);
}
handleChange = event => {
const content = event.target.value;
this.setState({ content: content });
};
hamdleSubmit = event => {
event.preventDefault();
alert(this.state.content);
this.setState({ content: "" });
};
}
export default Form;
こんな感じですね。挙動は、こんな感じになります。
ここまでできれば一旦OKです。
なので、Form.js
のコンストラク内のstateのcontentで定義している文字列は空白でもいいかもですね。
次は、いよいよ、アラートを出すのではなく、実際に、stateを変更して、List
コンポーネントを変更するような機能を作成していきます。
stateを変更する機能を作成する
今度は、テキストフォームにメモの内容を記述したら、List
コンポーネントにその値が反映されるようにしましょう。
手順は以下のような感じです。
- (色々なやり方がありますが今回は)
MemoApp.js
でテキストを入力してボタンを押したら- 関数(
addMemo
)が走るようにしましょう- 関数(
addMemo
)自体は、-
setState
が走るようにして - その中でstate(memosの配列に
Spread operator
で既存のものを残しつつ、新たな要素を追加します) - contentの内容は
Form.js
内のhamdleSubmit
関数の中で引数として渡します - 宣言元の
addMemo
は引数としてhamdleSubmit
関数から渡ってきたメモ内容(this.state.content
)を引数(content
)として受け取って、Spread operator
(3点)の後にカンマして、あらたなオブジェクトに渡す - そして、idも
this.state.nextId
もセットして、その後のsetState
内で 1たす項目を設けます
-
- 関数(
- 関数(
- 既存のstate(
memos
)にsubmit
した際、要素が加われば、コンストラクタで定義した既存のstate(memos
)は消しましょう
JSでは、Spread operator
(スプレッド構文)は頻出シンタックスなのでぜひ覚えてください。
また、今回実装したstate
を変更するメソッド(addMemo
)を親(MemoApp.js
)に持たせて、それを子(Form.js
)に渡して実行する方法はよくあるので難しいですが手に馴染ませてください。
子に渡っているのはhamdleSubmit
関数の中のthis.props.addMemo(this.state.content);
← この部分ですね。
それでは、実装していきましょう。
import React from "react";
import { render } from "react-dom";
import Form from "./components/Form";
import List from "./components/List";
class MemoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
memos: [],
nextId: 0
};
}
addMemo = content => {
this.setState({
memos: [...this.state.memos, { id: this.state.nextId, content: content }],
nextId: this.state.nextId + 1
});
};
render() {
return (
<div>
<h2>MemoApp</h2>
<Form addMemo={this.addMemo} />
<List memos={this.state.memos} />
</div>
);
}
}
export default MemoApp;
import React from "react";
import { render } from "react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = { content: "" };
}
render() {
return (
<div>
<h2>Form</h2>
<form onSubmit={this.hamdleSubmit}>
<input value={this.state.content} onChange={this.handleChange} />
<input type="submit" value="Add Memo" />
</form>
</div>
);
}
handleChange = event => {
const content = event.target.value;
this.setState({ content: content });
};
hamdleSubmit = event => {
event.preventDefault();
this.props.addMemo(this.state.content);
this.setState({ content: "" });
};
}
export default Form;
こんな感じですね。挙動は、こんな感じになります。
stateを削除する機能を作成する
次は、個々のメモを削除するような機能を作成していきます。
手順は以下のような感じです。
-
List.js
のコンポーネントにタグを設置 - 機能自体は
MemoApp.js
のaddMemo
の下にdeleteMemo
メソッドを作成していきましょう-
deleteMemo
メソッドは引数にmemosのid
を取れるようにしてください
-
- では、上記の呼び出しを
List.js
に定義したbuttonタグにハンドラはonClick
として、アロー関数をdeleteMemo
メソッドのコールバックとして実行してください - コール先の関数の中の処理ですが
- 配列(
memos
)をfilter
で処理して、引数として受け取ったid以外をTRUE
として返すような条件でにして、結果を変数filteredArray
に格納し、それをmemosのsetState
として渡す
- 配列(
それでは、実装していきましょう。
import React from "react";
import { render } from "react-dom";
import Form from "./components/Form";
import List from "./components/List";
class MemoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
memos: [],
nextId: 0
};
}
addMemo = content => {
this.setState({
memos: [...this.state.memos, { id: this.state.nextId, content: content }],
nextId: this.state.nextId + 1
});
};
deleteMemo = id => {
const filteredArray = this.state.memos.filter(memo => {
return memo.id !== id;
});
this.setState({ memos: filteredArray });
};
render() {
return (
<div>
<h2>MemoApp</h2>
<Form addMemo={this.addMemo} />
<List memos={this.state.memos} deleteMemo={this.deleteMemo} />
</div>
);
}
}
export default MemoApp;
import React from "react";
import { render } from "react-dom";
class List extends React.Component {
render() {
const list = this.props.memos.map(memo => {
return (
<li>
#{memo.id} - {memo.content}{" "}
<button onClick={() => this.props.deleteMemo(memo.id)}>delete</button>
</li>
);
});
return (
<div>
<h2>List</h2>
<ul>{list}</ul>
</div>
);
}
}
export default List;
こんな感じですね。挙動は、こんな感じになります。
冗長な部分をリファクタ
冗長な書き方になってしまっている部分をconst { ... } = this.state;
(props
でも同様)と定義してリファクタしてあげましょう。
また、この記述import { render } from "react-dom";
が不要なファイルはあるので、不要であれば消します。
そして、
Warning: Each child in a list should have a unique "key" prop.
というエラー対応をします。具体的には、List
コンポーネント’にある
id
を追加します。
<li key={memo.id}>
こんな感じですね。
それでは、実装していきましょう。
import React from "react";
import Form from "./components/Form";
import List from "./components/List";
class MemoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
memos: [],
nextId: 0
};
}
addMemo = content => {
const { memos, nextId } = this.state;
this.setState({
memos: [...memos, { id: nextId, content: content }],
nextId: this.state.nextId + 1
});
};
deleteMemo = id => {
const { memos } = this.state;
const filteredArray = memos.filter(memo => {
return memo.id !== id;
});
this.setState({ memos: filteredArray });
};
render() {
const { memos } = this.state;
return (
<div>
<h2>MemoApp</h2>
<Form addMemo={this.addMemo} />
<List memos={memos} deleteMemo={this.deleteMemo} />
</div>
);
}
}
export default MemoApp;
import React from "react";
class List extends React.Component {
render() {
const { memos, deleteMemo } = this.props;
const list = memos.map(memo => {
return (
<li key={memo.id}>
#{memo.id} - {memo.content}{" "}
<button onClick={() => deleteMemo(memo.id)}>delete</button>
</li>
);
});
return (
<div>
<h2>List</h2>
<ul>{list}</ul>
</div>
);
}
}
export default List;
長くなりましたが、Reactを使ったメモアプリ作成は以上です。参考にしてみてください。
参考
- 改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで | 山田 祥寛