本日はアプリ制作第二弾「メモ帳アプリ」を作ります。第三弾では「カウントダウンタイマー」、第四弾ではフォームアプリを作り、前回学んだSPAを実装します。
初めての人はお金持ちまでの道のり:0日目をまず読んでみてください!よろしくお願いします★
メモ帳アプリ
機能概要
メモの追加:ユーザーが入力したメモをリストに追加する。
メモの削除:追加したメモをリストから削除できるようにする。
メモの一覧表示:現在のメモがリスト形式で表示される。
今回のメモ帳アプリでは、Reactの機能を使って効率的にアプリを構築するためにNode.js command promptを使用します。
Node.js command promptを使う理由
今回のメモ帳アプリも前回のTodoリストアプリのようにHTMLだけでコードを書くこともできるのですが、今回はReactの機能を使って効率的にアプリを構築するためにNode.js command promptを使用します。
なぜReactを使うのか
- 効率的なコード構築:
- Reactのコンポーネントを使うと、アプリを小さな部品(コンポーネント)に分けて管理できます。これにより、コードの再利用が簡単になり、メンテナンスが楽になります。
- リアクティブなUIの構築:
- Reactでは、状態(state)を使って、データの変化に応じてUIを自動的に更新する仕組みがあるため、ユーザーの操作に対する反応が素早く行えます。
- 開発スキルの向上:
- Reactは業界で広く使われており、Reactの習得は他のプロジェクトやアプリ開発に役立ちます。今回のアプリではReactを練習し、スキル向上を目指します。
HTMLだけのアプリ vs Reactを使ったアプリ
★HTMLだけのTo-Doリストアプリ
- シンプルに、HTML、CSS、JavaScriptを使って作られたアプリです。
- ブラウザだけで動かせるので、特別な準備や設定は不要です。
- 新しい機能を追加するときには、すべて手作業でコードを追加する必要があります。
- たとえば「リストの項目を増やす」とか「見た目を変える」など、一つひとつの部分を自分でコードに書き込んでいきます。
★Reactを使ったメモ帳アプリ
Reactを使うと、効率よく、作りやすくなる仕組みがあります。
- コンポーネントがある
- Reactでは、アプリを小さなパーツ(コンポーネント)に分けられます。
- 例えば、「メモを書く部分」「メモを表示する部分」「削除ボタン」といったパーツごとに分けて作れます。
- 各パーツを一度作れば、他の場所でも何回でも使えるので、作るのが楽になります。
- データが変わると自動で画面も変わる
- Reactには「状態(state)」という仕組みがあって、メモやTo-Doリストのデータが変わると画面が自動で更新されます。
- 例えば、メモを追加すると自動的にリストが更新され、手作業で再表示させる必要がありません。
- 作り直しが簡単
- Reactは変更があっても、その部分だけ更新してくれるので、アプリを大きく作り直す必要がありません。
- 作り直しが簡単だから、もっと大きなアプリや新しい機能を追加しやすくなります。
簡単なまとめ
- HTMLだけのTo-Doリスト:全部自分で一から作っていく。少しずつ手作業で追加・変更していくイメージ。
- Reactのメモ帳:パーツを組み合わせて作る。データが変わると自動で画面が変わり、大きなアプリを作るときにも便利。
Reactを使うと、たくさんの機能やパーツが簡単に追加でき、アプリがどんどん成長しても管理しやすくなります。
実装手順①
ステップ1:Node.js command promptでReactプロジェクトを作る
Node.js command promptを開く:
- パソコンで「Node.js command prompt」を探してクリックして開きます。これはReactのプロジェクトを作るための特別な窓です。
デスクトップなど、アプリを作りたい場所に移動する:
cd Desktop
と入力してEnterを押すと、デスクトップ上にアプリのフォルダが作られます(別の場所にしたい場合は、フォルダに合わせて変更できます)。
Reactプロジェクトを作成するコマンドを入力:
- 次のコマンドを入力して、新しいReactアプリのプロジェクトを作ります。アプリの名前は好きに決めていいですが、ここでは「my-memo-app」としてみましょう。
※npx create-react-app 〇〇(プロジェクト名)で名前を決める
npx create-react-app my-memo-app
これでプロジェクトが作成され、src
(source)フォルダの中にApp.js
が自動で生成されます。
※npx create-react-app my-memo-app
で作られるのはApp.jsだけではなく、Reactアプリ全体のフォルダと基本ファイル一式です。App.js
はその中のメインファイルの一つです。
App.js
ファイルとは、Reactプロジェクトのメインコンポーネントのファイルです。通常、Reactアプリを作成すると、このファイルがプロジェクトの中に自動で作成され、**アプリ全体の入り口(エントリーポイント)**として機能します。
ステップ2:Reactプロジェクトの準備
プロジェクトのフォルダに入る:
- さっき作ったフォルダに移動するため、次のように入力してEnterを押します。
cd my-memo-app
アプリをスタート:
- Reactアプリをブラウザに表示するためのコマンドを入力します。
npm start
これでブラウザが自動的に開き、「Reactアプリ」が表示されます。この画面を見ながらコードを編集して、メモ帳アプリを作っていきます。
ステップ3:メモ帳アプリの中身を作る
VSCodeを開き、「App.js」ファイルを編集する:
ここ注意!
VSCode(Code.exe)を開く→左上のファイル→フォルダーを開く→my-memo-appフォルダを1クリック→フォルダーの選択
※my-memo-appをダブルクリックしてはダメ!
フォルダーの選択をクリックすると下の画面が表示されます。
- 左側のエクスプローラーのsrcをクリックすると「App.js」というファイルがあるのでダブルクリックする。このファイルでメモ帳アプリの中身を作ります。
- まず、以下のように基本的なメモ帳アプリの構造を作成しましょう。
function App() {
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input type="text" placeholder="メモを入力してください" />
<button>追加</button>
<ul>
{/* メモを表示するリストアイテムをここに追加 */}
</ul>
</div>
);
}
export default App;
1. function App() { ... }
の部分
- App という名前の「アプリの本体」を作っています。この中に、メモ帳の画面を表示するためのいろいろな部品が入っています。
2. <div className="App"> ... </div>
の部分
<div>
は「部屋」のようなもので、ここに画面に表示するものをまとめて入れています。className="App"
は、部屋に「App」という名前をつけて、「ここがアプリ全体ですよ!」とわかりやすくしています。
※Appというfunctionと同じ名前にする必要はない ですが、特に小さなアプリの場合、名前を統一しておくとわかりやすくなります。
3. <h1>メモ帳アプリ</h1>
の部分
<h1>
はタイトルの役割をしています。画面に大きな文字で「メモ帳アプリ」と表示されます。
4. <input type="text" placeholder="メモを入力してください" />
の部分
<input>
は入力ボックスです。ここにメモを書き込むことができます。type="text"
は、「この入力ボックスには文字を入力できますよ!」という指定です。ここで「text」としているので、文字や数字を自由に入力できるようになっています。placeholder="メモを入力してください"
は、入力ボックスにうっすらと表示される案内です。「ここにメモを書いてね!」とユーザーに教えてくれます。
5. <button>追加</button>
の部分
<button>
はボタンです。このボタンを押すことで、入力したメモがリストに追加される予定です(まだこの機能は追加していません)。
6. <ul> ... </ul>
の部分
<ul>
はメモのリストを表示する「リストの部屋」です。{/* メモを表示するリストアイテムをここに追加 */}
と書かれている部分は、「ここにメモをどんどん表示しますよ!」という意味です(コメントと呼ばれるもので、動作には影響しません)。
※ReactのJSX(JavaScript XML)では、通常のJavaScriptとは少し異なり、{/* ... */}
の形式でコメントを書きます。
※Reactのコード全体でJSXを使うことができますが、通常はreturn
の中にJSXを記述する のが一般的です。
全体の流れ
このアプリでは、まず入力ボックスにメモを書いて、「追加」ボタンを押すことで、メモをリストにどんどん追加していく、というイメージです。今はまだ基本の形だけですが、次に「メモを追加する機能」などを加えて、完成させていきます。
Visual Studio Codeの左上の三本線→ファイル→保存でコードが保存されるとReact Appの画面が自動的に以下の画像のようになり、現時点のアプリの状況を確認できます。以下が現時点でのアプリ画面です。入力後に追加ボタンを押しても何も起こりません。
なぜNode.jsとVSCodeの両方を使うのか?
- Node.jsで準備し、VSCodeで内容を作るため:
- Node.jsは、Reactアプリを始めるための環境(プロジェクトのフォルダやファイル)を作ってくれるツールです。
- VSCodeは、作成したReactプロジェクトの中で、HTMLやJavaScriptを使ってアプリの見た目や動きを書き込むために使います。
- 実際のアプリ開発を簡単にするため:
- Node.jsで作成したReactプロジェクトは、VSCodeでコードを変更すると、ブラウザが自動的に更新されて、リアルタイムで結果を確認できます。これにより、アプリの開発がスムーズに行えます。
まとめ
- Node.jsは、Reactプロジェクトを作成・準備してくれるツール。
- VSCodeは、そのプロジェクトのコードを書き換えたり編集するためのエディター。
実装手順②
ステップ1: 状態管理用のuseStateを追加
Reactでは、コンポーネントの状態を管理するためにuseState
というフックを使います。このアプリでは、「メモのリスト」と「入力されたメモの内容」を状態として管理する必要があります。
useState
をインポートします。- メモのリストを保存するための状態(
notes
)と、メモの入力内容を保存するための状態(input
)を追加します。
import React, { useState } from 'react';
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>{note}</li>
))}
</ul>
</div>
);
}
export default App;
1. import React, { useState } from 'react';
これは「React」というプログラムの一部を使うための準備です。useState
というのは、「状態」といって、アプリの中で覚えておきたいものを管理するためのツールです。
2. function App() {
App
という名前の「アプリ本体」を作っています。この中でメモ帳アプリがどう動くかを決めていきます。
3. const [notes, setNotes] = useState([]);
const [状態, 更新関数] = useState([]);
const
は、「この箱(変数)は名前を変えずに使い続けます」という決まりをつけて、変数(データを入れる箱)を作る言葉です。ここでnotes
というメモのリスト(紙のメモのようなもの)を作っています。useState([]);
で「最初は空っぽのメモのリストですよ」と設定しています。setNotes
というのは、メモを追加したり変えたりするときに使います。
notes
(状態):この変数は「メモのリスト」です。メモを追加するたびに、このリストの中にメモが増えていきます。setNotes
(更新関数):これは notes に新しいデータ(メモのリスト)を入れるためのボタンのような役割です。
※状態と更新関数はセットで名前を決める必要があります。例えば、状態をnotes
にした場合、更新関数も一緒にsetNotes
といった関連性がわかる名前にすると、コードの可読性が高くなります。useState([])
:notes
の中に、最初は[]
(空っぽのリスト) を入れています。何もメモがない状態を表しています。
つまり、**「最初は空のメモリスト(notes
)を用意して、あとでメモが増えたら setNotes
を使ってメモを追加します」**という意味です。
Reactの状態管理には const
が推奨
Reactでは、useState
で作成した状態の更新は setNotes
や setInput
のように専用の更新関数を使います。これにより、状態の管理が明確になり、const
が適しているのです。
4. const [input, setInput] = useState('');
ここでは、input
という名前で「入力された文字」を保存する場所を作っています。useState('');
で「最初は何も書かれていない状態」にしています。setInput
は、入力が変わったときにその内容を保存するために使います。
※useState
は、Reactで「メモリのように値を保存する場所」を作るための関数です。この関数を使うと、値を保存する「箱」と、その値を変えるための「スイッチ」が一緒に作られます。この「箱」の中の値が変わると、自動的に画面も更新されます。
input
(状態):この変数は「ユーザーが入力したメモ内容」を一時的に入れておく箱です。setInput
(更新関数):これはinput
に新しいデータ(入力された文字)を入れるためのボタンのような役割です。inputが箱で、setInputがスイッチです。
※この構造で、入力内容が変わるたびにsetInput
を使ってinput
を更新し、画面に新しい内容が反映されるようになります。useState('')
:input
の中に、最初は''
(空っぽの文字列) を入れています。まだ何も入力されていない状態を表しています。
つまり、**「最初は空の入力内容(input
)を用意して、文字が入力されたら setInput
を使ってその内容を保存します」**という意味です。
5. return ( ... )
return
の中で、アプリに表示する内容を決めています。ここに書かれたものが画面に出てきます。
6. <div className="App">
div
は「アプリ全体をまとめる箱」みたいなものです。この中にアプリの内容が全部入っています。
7. <h1>メモ帳アプリ</h1>
<h1>
は「アプリのタイトル」です。ここでは「メモ帳アプリ」というタイトルが表示されます。
8. <input type="text" ... />
<input>
は「文字を入力する場所」です。ここでユーザーがメモを入力できます。
value={input}
は、今書かれている内容を表示します。onChange
は、「入力ボックスに何か文字を入力するたびに、ある動きをさせたい」というときに使う特別な設定です。{(e) => ... }
の{}
は「この中はJavaScriptの特別な計算や動きがありますよ」という意味です。onChange
に何をするかを{}
の中に書きます。(e) => ...
という書き方は「アロー関数」といって、Reactでよく使います。この場合、(e)
というのは「イベント」のことです。文字を入力すると、その「イベント」が起こり、e
というものの中に「どんな文字が入力されたか」が入っています。
=onChange={function(e) { setInput(e.target.value); }}e.target.value
は「今入力された文字」を指します。setInput
は「input
に入れる内容を更新する」ための特別な関数です。setInput(e.target.value)
によって、input
という状態に「今入力された文字」が保存されます。onChange={(e) => setInput(e.target.value)}
は、何か文字が書き込まれるたびにinput
に保存します。placeholder="メモを入力してください"
は、何も書かれていないときに「メモを入力してください」という文字が薄く表示されます。
9. <button onClick={addNote}>追加</button>
- 「追加」ボタンです。
onClick={addNote}
は、ボタンを押したときにaddNote
という機能(メモを追加する機能)を動かす設定です。このaddNote
はまだ書いていないので、次に書く必要があります。 {}
は「この中はJavaScriptですよ」という意味です。JSX(HTMLに似た構文)の中にJavaScriptを埋め込むために使います。{}
の中に書くと、JavaScriptの変数や関数を使うことができます。addNote
は関数なので、{}
の中に書くことで「このボタンがクリックされたらaddNote
関数を実行する」という設定ができます。
※ここではまだaddNote
関数が定義されていないため、クリックしても何も起こりません。
10. <ul> ... </ul>
<ul>
は「メモのリスト(一覧)」です。
notes.map((note, index) => (...)
notes
は今までに追加したメモが入っているリストです(例えば、「買い物する」、「宿題をする」などが入っています)。- map は、配列(ここでは notes 配列)の各要素に対して処理を行い、その結果を新しい配列として返すための関数です。(※
map
は JavaScriptの組み込みメソッド)- 配列の各要素に関数を適用:
notes
配列の各要素(ここでは各note
)に対して、指定した関数((note, index) => (<li key={index}>{note}</li>)
)を順番に適用します。 - 新しい配列を返す:
map
関数が生成した<li>
要素のリストを新しい配列として返し、React はその配列をレンダリングします。 - インデックスを使ってユニークなキーを指定:各要素に
key
属性を付けるためにindex
を利用しています。React がレンダリングや再描画を効率的に行うために、key
属性は必須です。
- 配列の各要素に関数を適用:
(note, index) => (...)
の部分は、リストの中の「メモ」(note
)と、その順番(index
)を1つずつ取り出して、「このメモを画面に表示するよ!」という指示を書いています。
map
関数の主な機能
- 配列の各要素に関数を適用:配列のすべての要素に対して指定した関数を順に適用します。
- 新しい配列を返す:元の配列は変更せず、新しい配列を返します(元の配列はそのまま保持されます)。
- コールバック関数の引数:
- 要素(element):現在の配列の要素。
- インデックス(index):現在の要素が配列の何番目かを示す番号。
- 配列(array):元の配列全体。
- 便利なデータ変換:配列の各要素に対して何らかの処理をして、新しい形式のデータに変換するのに役立ちます。
map
は、新しい配列を作るときに効率的に使える組み込みメソッドです。
<li key={index}>{note}</li>
<li>
は「リストのアイテム」という意味で、メモを1つずつリスト形式で表示します。key={index}
- これはReactが「どのメモがどれか」を覚えるための目印です。たとえば、1番目のメモと2番目のメモがわかるように、1つ1つに番号をつけています(
index
がその番号です)。
- これはReactが「どのメモがどれか」を覚えるための目印です。たとえば、1番目のメモと2番目のメモがわかるように、1つ1つに番号をつけています(
{note}
- これは「メモの内容」をそのまま画面に表示する部分です。たとえば、「買い物する」というメモが入っていれば、ここに「買い物する」が表示されます。
{notes.map((note, index) => ( ... ))}
は、notes
に入っているメモを1つずつ取り出して、画面に表示します。<li key={index}>{note}</li>
は、メモを1つずつ<li>
の中に入れて表示するところです。
11. export default App;
このアプリを他の場所でも使えるようにしています。このメモ帳アプリで export default App;
は必須 です。
- このメモ帳アプリは、Reactアプリのエントリーポイント(最初に表示する部分)として
App
コンポーネントを使っています。 - 例えば、
index.js
ファイルでApp
コンポーネントを読み込み、画面に表示する必要があります。 export default App;
がないと、index.js
など他のファイルからApp
をインポートして使用できなくなります。- Reactアプリでは通常、
index.js
からメインのコンポーネント(この場合はApp
)を読み込んで、アプリ全体をブラウザに表示します。 export default App;
を付けておかないと、index.js
からApp
をインポートできず、アプリを正常に表示できなくなります。
export default App;
は function App
(コンポーネント)を指します。
className="App"
はCSSクラス名で、スタイルを適用するためだけに使われているものです。
ステップ2:addNote関数の追加
このコードには addNote
関数がまだ定義されていないので、ボタンをクリックしたときに実行する addNote
関数を追加する必要があります。このままのコードだと以下のようなエラーが出ます。
addNote
は、ユーザーが入力したメモをメモリストに追加するための関数です。この関数を追加することで、ボタンをクリックしたときに入力内容がリストに追加されるようになります。
import React, { useState } from 'react';
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
// addNote 関数を定義
const addNote = () => { // addNoteという関数を定義
if (input.trim()) { // もしinputが空白以外の内容を持っているなら
setNotes([...notes, input]); // 現在のメモリストに新しいメモ(input)を追加する
setInput(''); // 入力欄を空にする
}
};
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>{note}</li>
))}
</ul>
</div>
);
}
export default App;
以下のように addNote
関数を追加して、ボタンをクリックしたときにメモがリストに追加されるようにします。
const addNote = () => { ... };
addNote
関数は、メモをリストに追加するための機能を持っています。
if (input.trim()) { ... }
trim()
は、文字列の前後にある空白を取り除くための関数です。
input = " 勉強する ";
input.trim(); // "勉強する" (空白が取り除かれる)
※trimはJavaScript の組み込み関数- 「もし
input
のtrim
が空白以外の内容を持っているなら」、中の処理を実行します。 input.trim()
は、input
の前後の空白を取り除いた文字列を返します。if (input.trim())
の部分では、空白以外の文字が入力されている場合のみ条件がtrue
になります。空白だけなら条件はfalse
になります。
setNotes([...notes, input]);
- 「
notes
の現在のリストにinput
の内容を追加し、それをsetNotes
で新しいリストとして保存する」 - 現在の
notes
リストに、新しいメモ(input
に入力された内容)を追加します。 ...notes
は、現在のメモのリストをすべて展開し、新しいメモ(input
)を追加して更新する書き方です。- もし
notes
が["メモ1", "メモ2"]
でinput
が"メモ3"
なら、[...notes, input]
は["メモ1", "メモ2", "メモ3"]
になります。
- 「
setInput('');
- メモを追加した後、
input
の内容を空の文字列にして、入力欄をクリアします。
- メモを追加した後、
これで以下の画像のようにメモをリスト化できます。
実装手順③
ステップ1:メモを削除する機能の追加
import React, { useState } from 'react';
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
// メモを追加する関数
const addNote = () => {
if (input.trim()) { // 空白だけでない場合にメモを追加
setNotes([...notes, input]); // 新しいメモをリストに追加
setInput(''); // 入力欄を空にする
}
};
// メモを削除する関数
const deleteNote = (index) => {
// index(削除したいメモの番号)を受け取る関数、deleteNoteを作成
const newNotes = notes.filter((note, i) => i !== index);
// filterを使って、indexと一致しないメモだけを新しいリストに残す
setNotes(newNotes);
// 新しいメモのリストを保存する
};
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>
{note}
<button onClick={() => deleteNote(index)}>削除</button> {/* 削除ボタンを追加 */}
</li>
))}
</ul>
</div>
);
}
export default App;
deleteNote
関数:
deleteNote
という関数を新しく作りました。この関数は、メモを削除するためのものです。index
は削除するメモの位置を表します(何番目のメモかを示す番号)。notes.filter((note, i) => i !== index)
は、リストの中でindex
と一致しないメモだけを新しいリストnewNotes
に残す、という意味です。アロー関数を使わない場合は以下の通りです。
const newNotes = notes.filter(function(note, i) {return i !== index;});
filter
メソッド:
filter
は、条件に合う要素だけを残すためのJavaScriptの関数です。この場合、i !== index
(i
がindex
と違う)という条件でフィルタをかけています。つまり、index
のメモだけを除外して新しいリストを作ります。filter
はJavaScriptの組み込み関数です。
削除ボタン:
- 「
notes
配列の各要素note
とその位置index
を使って、新しい配列を生成します。」 notes.map((note, index) => ...
の中で、各メモに削除
ボタンを追加しています。onClick={() => deleteNote(index)}
は、削除ボタンがクリックされたときにdeleteNote
関数が呼ばれ、メモが削除されるようにしています。{note}
:Reactでは{}
の中に変数を入れると、その値が JSX 内で表示されます。ここでは、各メモの内容を画面に表示するために{note}
と書いています。
=の使い方について
1 == '1' // true (値は同じなので型変換が行われて比較される)
1 === '1' // false (型が異なるため等しくない)
1 === 1 // true (値も型も同じ)
このコードでメモを削除できるようになります。
ステップ2:編集ボタンの追加
import React, { useState } from 'react';
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
const [editingIndex, setEditingIndex] = useState(null); // 編集中のメモのインデックス
const [editingInput, setEditingInput] = useState(''); // 編集用の入力欄の内容
// メモを追加する関数
const addNote = () => {
if (input.trim()) { // 空白だけでない場合にメモを追加
setNotes([...notes, input]);
setInput('');
}
};
// メモを削除する関数
const deleteNote = (index) => {
const newNotes = notes.filter((note, i) => i !== index);
setNotes(newNotes);
};
// 編集ボタンがクリックされたときの関数
const startEditing = (index) => {
setEditingIndex(index); // 編集中のメモのインデックスを設定
setEditingInput(notes[index]); // 編集用の入力欄に現在のメモの内容を設定
};
// 編集内容を保存する関数
const saveEdit = () => {
const newNotes = [...notes];
newNotes[editingIndex] = editingInput; // 編集された内容でリストを更新
setNotes(newNotes);
setEditingIndex(null); // 編集モードを解除
setEditingInput(''); // 編集用入力欄をクリア
};
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>
{editingIndex === index ? (
<>
<input
type="text"
value={editingInput}
onChange={(e) => setEditingInput(e.target.value)}
/>
<button onClick={saveEdit}>保存</button>
</>
) : (
<>
{note}
<button onClick={() => startEditing(index)}>編集</button>
<button onClick={() => deleteNote(index)}>削除</button>
</>
)}
</li>
))}
</ul>
</div>
);
}
export default App;
メモの編集機能を実現する
const [editingIndex, setEditingIndex] = useState(null);
- 「
useState
を使ってeditingIndex
(編集中のメモの番号)を初期値null
で設定し、更新用の関数setEditingIndex
を定義します。」 editingIndex
(箱):今、編集中のメモがどのインデックス(番号)かを保存するための変数です。setEditingIndex
(スイッチ):editingIndex
の値を更新するための関数です。useState(null)
:初期値としてnull
を指定しているので、最初はどのメモも編集していない状態です。
※たとえば、2番目のメモが編集されるとeditingIndex
は 1 になります。
※useState は、Reactで「メモリのように値を保存する場所」を作るための関数です。この関数を使うと、値を保存する「箱」と、その値を変えるための「スイッチ」が一緒に作られます。この「箱」の中の値が変わると、自動的に画面も更新されます。
- 「
const [editingInput, setEditingInput] = useState('');
editingInput
(箱):編集中のメモの内容を保存するための変数です。実際に編集された内容をこの変数に一時的に保存します。setEditingInput
(スイッチ):editingInput
の値を更新するための関数です。useState('')
:編集欄が最初は空なので、''
(空の文字列)で初期化しています。
使い方の例
- 編集ボタンをクリックすると、
editingIndex
に編集するメモの番号が入り、editingInput
にそのメモの内容が入ります。 - 編集内容が保存されると、
editingIndex
は再びnull
に戻り、editingInput
は空にリセットされます。
この2つの状態を使うことで、どのメモが編集中で、編集欄にどの内容が入っているかを管理できるようになります。
編集ボタンがクリックされたときの関数
const startEditing = (index) => { ... }
startEditing
という関数を作っています。この関数は、どのメモを編集するのかを設定するためのものです。(index)
は、クリックされたメモが「何番目」なのかを表します。この番号をindex
として関数に渡しています。
setEditingIndex(index);
setEditingIndex
は、「編集中のメモの番号(インデックス)」を保存するための特別な関数です。- 例えば、2番目のメモを編集しようとすると、
index
が 2 になり、setEditingIndex(2);
が実行されます。 - これによって、Reactは「今2番目のメモを編集中だ」と認識します。
setEditingInput(notes[index]);
setEditingInput
は、「編集するメモの内容」を入力欄に表示するための関数です。notes[index]
は、notes
リストの中で、クリックされたメモの内容を取り出します。- 例えば、3番目のメモが「宿題をする」なら、
notes[2]
で「宿題をする」を取得し、setEditingInput("宿題をする");
として入力欄にその内容を表示します。
具体的な流れ
たとえば、3番目のメモ「宿題をする」を編集したい場合、次のように動きます。
- 「編集」ボタンをクリック すると、
startEditing
関数が呼ばれ、index
が 2(3番目のメモ)として渡されます。 setEditingIndex(2);
が実行され、Reactは「3番目のメモが編集中である」と認識します。setEditingInput(notes[2]);
で、メモ「宿題をする」の内容が入力欄に表示されます。
まとめ
startEditing
は、どのメモを編集するのかを決めて、内容を入力欄に表示するための関数です。- 編集するメモの番号(
index
)と、メモの内容(notes[index]
)をセットしています。 - これにより、編集するメモがクリックされたときに内容が入力欄に表示され、編集ができるようになります。
編集内容を保存する関数
const saveEdit = () => { ... }
saveEdit
という名前の関数を定義しています。この関数は、「編集した内容を保存する」役割を持ちます。
const newNotes = [...notes];
notes
は、現在のメモのリストです。この行でnotes
の内容をコピーした新しいリストnewNotes
を作成しています。[...notes]
というスプレッド構文を使うことで、現在のメモを変更せず、新しい配列newNotes
を作ります。こうすることで、Reactの状態管理が正しく働きます。
スプレッド構文の説明とその役割
[...notes]
というスプレッド構文を使うと、notes
という配列の「中身」をそのままコピーして新しい配列 newNotes
を作ります。
これをする理由は、「元の notes
配列をそのまま残しつつ、内容が同じ新しい配列を作るため」です。Reactでは、このように元の状態(配列)を直接変更せず、新しい状態を作ることで、変更を正しく認識し、画面が更新されます。
例
もし notes
が [1, 2, 3]
なら、[...notes]
は [1, 2, 3]
のコピーを新しく作るという意味になります。
newNotes[editingIndex] = editingInput;
editingIndex
は今編集しているメモの番号です。editingInput
は編集された内容です。- ここで、
newNotes
のeditingIndex
番目にeditingInput
(編集内容)を代入して、newNotes
の中身を新しい内容に更新します。 - たとえば、2番目のメモが編集されているならば、その内容が
newNotes[2]
に新しいテキストとして保存されます。
setNotes(newNotes);
- 更新した
newNotes
をsetNotes
関数で保存し、Reactに「メモのリストが変更された」と認識させます。 - これにより、画面に表示されるメモが新しい内容で更新されます。
- 更新した
setEditingIndex(null);
- 編集が完了したので、
editingIndex
をnull
にします。 - これにより、どのメモも編集中ではないと判断され、編集モードが解除されます。
- 編集が完了したので、
setEditingInput('');
- 編集用の入力欄(
editingInput
)の内容を空の文字列''
にします。 - これにより、編集が終わった後に入力欄がクリアされ、次の編集に備えることができます。
- 編集用の入力欄(
具体例
たとえば、3番目のメモ「宿題をする」を「買い物に行く」に変更するとしましょう。
newNotes
に現在のメモリストをコピー。newNotes[2] = "買い物に行く"
で3番目のメモを更新。setNotes(newNotes);
で更新されたメモを保存。- 編集モードを解除し、入力欄をクリアして完了。
まとめ
この関数は、「編集したメモを新しい内容で保存し、編集を終了する」ためのもので、Reactがメモの変更を認識し画面に反映する仕組みになっています。
編集中であれば編集用の入力欄と保存ボタンを表示する
三項演算子
条件 ? 真の場合の処理 : 偽の場合の処理
※条件が成り立てば真の処理をし、成り立たない場合は偽の処理をします。
※三項演算子は、条件が真か偽かで実行する処理を切り替えるための簡潔な書き方です。これにより、1行で条件分岐を簡潔に書くことができるので、UIの表示を条件によって切り替える場合に便利です。
- 条件
editingIndex === index - 真の場合の処理
( <> <input type="text" value={editingInput} onChange={(e) => setEditingInput(e.target.value)} /> <button onClick={saveEdit}>保存</button> </> ) - 偽の場合の処理
( <> {note} <button onClick={() => startEditing(index)}>編集</button> <button onClick={() => deleteNote(index)}>削除</button> </> )
{editingIndex === index ? ... : ... }
:- これは三項演算子と呼ばれる構文で、「もし
editingIndex
がindex
と等しいなら最初の内容を表示、そうでない場合は2番目の内容を表示」という条件を設定しています。 editingIndex === index
は、「現在表示しているメモが編集中かどうか」を確認するための条件です。- もし
editingIndex
がindex
と等しい場合、そのメモは現在編集中だと判断します。
- これは三項演算子と呼ばれる構文で、「もし
<input>
と<button>
(真の場合の表示):<>
と</>
はフラグメントで、複数の要素を一つにまとめています。<input type="text" value={editingInput} ... />
:- これは、メモの内容を編集するための入力欄です。
value={editingInput}
で現在の編集内容を表示し、onChange
イベントで編集内容が変更されるたびにeditingInput
を更新します。
<button onClick={saveEdit}>保存</button>
:- 「保存」ボタンで、クリックすると
saveEdit
関数が実行され、編集内容が保存されます。
- 「保存」ボタンで、クリックすると
: ( ... )
の部分(偽の場合の表示):- この部分は、「編集中でない場合の表示内容」を書くための場所です。
- 例えば、通常のメモ表示や「編集」「削除」ボタンなどがここに入ります。
まとめ
このコードにより、メモが編集中であれば「編集用の入力欄と保存ボタン」が表示され、編集中でない場合は通常のメモ表示に切り替わる仕組みになります。
このコードで編集ができるようになります。編集ボタンをクリックすると二枚目の画像のようになり、編集した新しいメモを保存できます。
ステップ3:日時情報を追加する
import React, { useState } from 'react';
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
const [editingIndex, setEditingIndex] = useState(null); // 編集中のメモのインデックス
const [editingInput, setEditingInput] = useState(''); // 編集用の入力欄の内容
// メモを追加する関数
const addNote = () => {
if (input.trim()) {
const newNote = {
content: input,
timestamp: new Date().toLocaleString()
};
setNotes([...notes, newNote]);
setInput('');
}
};
// メモを削除する関数
const deleteNote = (index) => {
const newNotes = notes.filter((note, i) => i !== index);
setNotes(newNotes);
};
// 編集ボタンがクリックされたときの関数
const startEditing = (index) => {
setEditingIndex(index);
setEditingInput(notes[index].content);
};
// 編集内容を保存する関数
const saveEdit = () => {
const newNotes = [...notes];
newNotes[editingIndex].content = editingInput;
setNotes(newNotes);
setEditingIndex(null);
setEditingInput('');
};
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>
{editingIndex === index ? (
<>
<input
type="text"
value={editingInput}
onChange={(e) => setEditingInput(e.target.value)}
/>
<button onClick={saveEdit}>保存</button>
</>
) : (
<>
<div>{note.content}</div>
<small>{note.timestamp}</small>
<button onClick={() => startEditing(index)}>編集</button>
<button onClick={() => deleteNote(index)}>削除</button>
</>
)}
</li>
))}
</ul>
</div>
);
}
export default App;
現在のコードでは、メモの内容を直接配列に追加していますが、メモの内容だけでなく日時も一緒に保存するために、オブジェクトとして保存します。
// メモを追加する関数
const addNote = () => {
if (input.trim()) { // 空白だけでない場合にメモを追加
setNotes([...notes, input]);
setInput('');
}
};
⇓
// メモを追加する関数
const addNote = () => {
if (input.trim()) {
const newNote = {
content: input,
timestamp: new Date().toLocaleString()
};
setNotes([...notes, newNote]);
setInput('');
}
};
const newNote
は、新しいメモを作るための「箱」を作っていると考えてください。この箱の中に「内容」と「日時」を入れます。newNote
はオブジェクトとして定義されています。content: input
は、メモの「内容」を意味しています。ここではinput
(入力されたメモの内容)をそのまま入れています。content
はキー、input
はその値です。timestamp: new Date().toLocaleString()
は、メモが作られた「日時」を表しています。new Date()
は「今の日時」を取得し、toLocaleString()
で読みやすい形式に変えています(例えば、「2024/11/4 14:30」のような形)。timestamp
はキーで、「日時」を示します。値としてnew Date().toLocaleString()
が使われています。new Date()
はJavaScriptの組み込み関数で、現在の日時を取得し、toLocaleString()
は、Date
オブジェクトの組み込みメソッドで、日時をわかりやすい形式に変換します(例えば、「2024/11/4 14:30」など)。
※キーと値の組み合わせをひとまとめにしたものを オブジェクト と呼びます。オブジェクトは{ }
で囲まれ、内部に プロパティ と呼ばれる「キーと値のペア」を持っています。
まとめ
このコードは、「メモの内容」と「いつ作ったかの日時」をセットで保存するためのものです。
notes.map
の中で、note
という変数がオブジェクトになったため、note.content
と note.timestamp
を使って内容と日時をそれぞれ表示します。
<>
{note}
<button onClick={() => startEditing(index)}>編集</button>
<button onClick={() => deleteNote(index)}>削除</button>
</>
⇓
<>
<div>{note.content}</div>
<small>{note.timestamp}</small>
<button onClick={() => startEditing(index)}>編集</button>
<button onClick={() => deleteNote(index)}>削除</button>
</>
<div>{note.content}</div>
<div>
は、HTMLのタグの一つで、内容を囲むための「箱」みたいなものです。{note.content}
の部分は、JavaScriptのコードをHTMLの中で使うために{ }
で囲まれています。note.content
は、そのメモの「内容」(テキスト)を指しています。これは、前に定義したオブジェクトnewNote
のcontent
プロパティに対応しています。- つまり、
note.content
の部分にはメモのテキストが入っており、それを<div>
タグで囲んで表示します。
<small>{note.timestamp}</small>
<small>
は、HTMLのタグで、文字を小さく表示するために使われます。{note.timestamp}
は、そのメモの「作成日時」を指しています。これもnewNote
の中のtimestamp
プロパティに対応しています。note.timestamp
には、そのメモが作成された日時が入っており、これを<small>
タグで囲んで、小さな文字として表示します。
まとめ
<div>{note.content}</div>
はメモの内容(テキスト)を表示する部分。<small>{note.timestamp}</small>
は作成日時を小さな文字で表示する部分です。
このコードによって、各メモのテキストと作成日時が、それぞれのHTML要素で画面に表示されます。
{note}
だけではなく <div>{note.content}</div>
としている理由は、メモに関する情報が「内容」と「日時」に分かれたためです。
元のコードでは、note
はただの文字列(例えば "海に行く"
など)で、メモの内容だけを表していました。しかし、新しいコードでは note
はオブジェクトであり、content
と timestamp
という2つの情報(プロパティ)を持っています。
具体的な変更内容
- 元のコード:
{note}
は文字列を直接表示していた。 - 新しいコード:
note
は{ content: "海に行く", timestamp: "2024-11-04 14:00" }
のようなオブジェクトになり、その中のcontent
プロパティを{note.content}
で表示し、timestamp
プロパティを{note.timestamp}
で表示するようになった。
まとめ
<div>{note.content}</div>
にしないと、note
がオブジェクトであるため、直接 {note}
と書くと、[object Object]
という表示になってしまいます。note.content
とすることで、オブジェクトの中の「内容」部分だけを取り出して表示することができるわけです。
これでメモを追加した日時が表示されるようになります。
このメモ帳アプリは SPA(シングルページアプリケーション) の仕組みを含んでいます。
なぜSPAといえるのか?
- ページのリロードがない
- メモを追加したり、削除したり、編集したりしても、ページ全体をリロードせずに内容が更新されます。これはReactが提供する仕組みで、アプリの特定の部分だけを更新します。
- Reactの状態管理
- Reactの
useState
フックを使って、メモの内容を管理しており、ユーザーが操作した内容がすぐに画面に反映されます。 - これはJavaScriptがバックグラウンドで動いて、必要な部分のみを更新しているからです。
- Reactの
- 単一のHTMLファイルで動作
- 通常、SPAでは1つのHTMLファイル(
index.html
など)で全ての表示内容を管理します。このアプリも、index.html
の中でReactが動作し、ページの一部だけを更新しているので、結果としてSPAの形をとっています。
- 通常、SPAでは1つのHTMLファイル(
まとめ
このメモ帳アプリは、ページ全体のリロードがなく、ユーザーの操作に応じて特定の部分だけが更新される仕組みが含まれているため、SPAといえます。
このコードをwordpressのカスタムHTMLに貼り付けるには以下の①を②に変える必要があります。
①
import React, { useState } from 'react';
②
<!-- ReactとReactDOMのスクリプトを読み込む -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<!-- Babelを使用してJSXを解釈させる -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<div id="react-root"></div> <!-- Reactアプリのマウント先 -->
<script type="text/babel">
const { useState } = React;
①の import React, { useState } from 'react';
というコードは、Reactをモジュールとしてインポートする構文です。これは通常、JavaScriptのモジュールをサポートしている環境(例えば、Node.jsやモジュールバンドラーを使用している環境)で使いますが、WordPressのカスタムHTMLブロックではそのまま使えません。
②のように <script>
タグを使ってReactとReactDOMを直接読み込むことで、WordPress環境でもReactの機能を使えるようにしています。この方法では、Reactがグローバルオブジェクトとして React
として提供され、{ useState }
のように直接参照して使用できます。
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
:React本体を読み込みます。<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
:ReactDOMを読み込みます。<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
:Babelを使用してブラウザ上でJSXを解釈できるようにしています。
これにより、Reactの useState
フックなどの機能をWordPress上でも使用できるようになります。
あとこのメモデータをローカルストレージに保存するには以下のコードを修正する必要があります。
追加されたコード
// useEffectの追加:ページが読み込まれたときにローカルストレージからメモを取得
useEffect(() => {
const savedNotes = JSON.parse(localStorage.getItem('notes'));
if (savedNotes) {
setNotes(savedNotes);
}
}, []);
// ローカルストレージに保存しつつ状態を更新する関数の追加
const saveNotesToLocalStorage = (newNotes) => {
localStorage.setItem('notes', JSON.stringify(newNotes));
setNotes(newNotes);
};
修正されたコード
addNote、deleteNote、saveEdit関数内でsetNotesを削除し、代わりにsaveNotesToLocalStorageを使用してローカルストレージに保存。
addNote
関数(修正後)
const addNote = () => {
if (input.trim()) {
const newNote = {
content: input,
timestamp: new Date().toLocaleString()
};
const newNotes = [...notes, newNote];
saveNotesToLocalStorage(newNotes); // ローカルストレージに保存し、状態も更新
setInput('');
}
};
saveNotesToLocalStorageの役割
- ローカルストレージにデータを保存
javascript
コードをコピーする
localStorage.setItem('notes', JSON.stringify(newNotes));
localStorage.setItemを使って、メモデータをローカルストレージに保存します。
notesというキーで保存し、次回ページが読み込まれたときにこのデータを取り出せるようにします。
JSON.stringifyを使って、配列(またはオブジェクト)形式のデータを文字列に変換しています。ローカルストレージには文字列しか保存できないため、この変換が必要です。
- Reactの状態を更新
javascript
コードをコピーする
setNotes(newNotes);
setNotesを使って、Reactの状態notesも更新します。
状態が更新されることで、Reactが再レンダリングを行い、最新のメモ内容が画面に表示されます。
なぜsaveNotesToLocalStorageを使うのか?
メモデータの追加、削除、編集があるたびにこのsaveNotesToLocalStorage関数を呼ぶことで、以下の2つの処理を同時に行えます。
ローカルストレージへのデータ保存(ページ更新後もデータが保持される)
Reactの状態更新(画面に即時反映される)
これにより、メモの内容が常にローカルストレージとReactの状態の両方に反映されるようになり、ページを更新してもデータが消えない仕様が実現されています。
deleteNote
関数(修正後)
const deleteNote = (index) => {
const newNotes = notes.filter((note, i) => i !== index);
saveNotesToLocalStorage(newNotes); // ローカルストレージに保存
};
saveEdit
関数(修正後)
const saveEdit = () => {
const newNotes = [...notes];
newNotes[editingIndex].content = editingInput;
saveNotesToLocalStorage(newNotes); // ローカルストレージに保存
setEditingIndex(null);
setEditingInput('');
};
変更された部分
// 修正前
const { useState } = React;
// 修正後
const { useState, useEffect } = React;
以下が修正したコード全体です。
<!-- ReactとReactDOMのスクリプトを読み込む -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<!-- Babelを使用してJSXを解釈させる -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<div id="react-root"></div> <!-- Reactアプリのマウント先 -->
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
const [notes, setNotes] = useState([]); // メモのリストを保存するための状態
const [input, setInput] = useState(''); // メモの入力を保存するための状態
const [editingIndex, setEditingIndex] = useState(null); // 編集中のメモのインデックス
const [editingInput, setEditingInput] = useState(''); // 編集用の入力欄の内容
// ページが読み込まれたときにローカルストレージからメモを取得
useEffect(() => {
const savedNotes = JSON.parse(localStorage.getItem('notes'));
if (savedNotes) {
setNotes(savedNotes);
}
}, []);
// メモをローカルストレージに保存する関数
const saveNotesToLocalStorage = (newNotes) => {
localStorage.setItem('notes', JSON.stringify(newNotes));
setNotes(newNotes);
};
// メモを追加する関数
const addNote = () => {
if (input.trim()) {
const newNote = {
content: input,
timestamp: new Date().toLocaleString()
};
const newNotes = [...notes, newNote];
saveNotesToLocalStorage(newNotes); // 新しく追加された部分:ローカルストレージに保存し、状態も更新
setInput('');
}
};
// メモを削除する関数
const deleteNote = (index) => {
const newNotes = notes.filter((note, i) => i !== index);
saveNotesToLocalStorage(newNotes); // 新しく追加された部分:ローカルストレージに保存
};
// 編集ボタンがクリックされたときの関数
const startEditing = (index) => {
setEditingIndex(index);
setEditingInput(notes[index].content);
};
// 編集内容を保存する関数
const saveEdit = () => {
const newNotes = [...notes];
newNotes[editingIndex].content = editingInput;
saveNotesToLocalStorage(newNotes); // 新しく追加された部分:ローカルストレージに保存
setEditingIndex(null);
setEditingInput('');
};
return (
<div className="App">
<h1>メモ帳アプリ</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メモを入力してください"
/>
<button onClick={addNote}>追加</button>
<ul>
{notes.map((note, index) => (
<li key={index}>
{editingIndex === index ? (
<>
<input
type="text"
value={editingInput}
onChange={(e) => setEditingInput(e.target.value)}
/>
<button onClick={saveEdit}>保存</button>
</>
) : (
<>
<div>{note.content}</div>
<small>{note.timestamp}</small>
<button onClick={() => startEditing(index)}>編集</button>
<button onClick={() => deleteNote(index)}>削除</button>
</>
)}
</li>
))}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('react-root'));
</script>
本日の感想
ものすごく苦労して作った割に出来上がったアプリはものすごいシンプル。基礎を学ぶためには仕方がないことだけどつまらないアプリの作成はしんどいです。入力したメモをローカルストレージに保存して、画面を更新してもメモが消えないようにするコード変更も地味に難しい。これだけのことになんでこんな大幅な変更が必要なのかと思ってしまいます。
録画していたテレビ番組を見たら、写真データをAIに読み込ませて指示を出すと、その指示通りに写真に写った人物が動き出すというアプリが紹介されていました。これからどんどん今までになかったものがAIによって作られていくと考えると、メモ帳アプリくらいしか作れない自分のレベルに焦りを感じます。
25-26日目の記事はこちら
SPA(Single Page Application)について 25-26日目 2024/10/31-11/1(お金持ちまでの道のり)
30日目の記事はこちら
カウントダウンタイマーアプリの制作 30日目 2024/11/5(お金持ちまでの道のり)
ここに初めて来た人はゼヒ0日目を読んでください。プログラミング知識0のおじさんがお金持ちになるまでのプランと熱い思いが載っています。私と一緒に運要素を排除したゴリゴリの脳筋プレイでお金持ちを目指しませんか?
0日目の記事はこちら
お金持ちまでの道のり:0日目
コードの読み方が全く分からないという方は以下の記事がオススメです。コードの読み方の説明が一番詳しく書かれてある記事です。
ToDoリストアプリの作成 12-15日目 2024/10/18-21(お金持ちまでの道のり)
コメント