React.jsの練習で、Todoアプリを作りました。
備忘録メモということで、ここに流れを書いていきます。
とても人気なjavascriptライブラリなのでどんどん練習して、習得していきたいと思います。
今回は見た目は何も扱っていないので、デザインについては見て見ぬ振りしてくださいww
ReactでTodoアプリを作る
todo-app --node_modules --package.json --public --index.html --manifest.json --README.md --src --index.js --registerServiceWorker.js --components --addtask.js --applist.js --main.js
ファイル構造はこんな感じです。
ターミナルで
Reactをグローバルにインストールした後に、
create-react-app todo-app
で開発環境を作っていきます。
詳しくはReactの公式ページにて
こんな感じで少し時間がかかります。
完了後、cd todo-appで作成したディレクトリ(todo-app)に移動し、npm startで開発開始です〜
ブラウザが起動し、こんな画面になればオケーです。
まず使わないファイルを消していきます。
faviconは後から設定してオッケーですのでとりあえずそのままにしてます。
srcフォルダの中の
App.js
App.test.js
App.css
index.css
logo.svg
を消します。
するとこんな感じでエラーが出ます。
index.jsでApp.css、index.css、logo.svgとか読み込んでいるのに消したから無いやん〜って言われているので、
import logo from ‘./logo.svg’;
import ‘./App.css’;
import ‘./index.css’;
など、消したファイルを読み込んでいる箇所を全部消します。
ここでブラウザを確認するとエラーが無くなり真っ白の画面になります。
*これはエラーではなく、全て中身を空にしたから真っ白です。
srcの中にcomponentsフォルダを作ります
この中に各コンポーネントのファイルを格納していきます。
*一応index.htmlのdiv内のid=“root”をid=“todo”にしときます。
こんな感じのファイル構造になればオケーです。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Todo} from './components/main'
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<Todo />, document.getElementById('todo'));
registerServiceWorker();
後々作るmain.jsからTodoコンポーネントをimportしています。
それをindex.html内のid=“todo”にポイっとしている感じです。
componentsフォルダの中にmain.jsを作成
main.js
import React from 'react’;
import {AddNewTask} from './addtask’;
import {ToDoAppList} from './applist'
export class Todo extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask />
<ToDoAppList />
</div>
);
}
}
AddNewTaskには新しいタスクを追加するコンポーネント(addtask.js) ToDoAppListには追加したタスクを表示・並べるコンポーネント(applist.js)がきます。
ざっくり言うと、main.jsにそれぞれのコンポーネントが渡され、TodoコンポーネントとしてApp.jsにimportされ、レンダーされる感じです。
addtask.js
import React from 'react';
export class AddNewTask extends React.Component {
constructor() {
super();
}
render() {
return (
<form>
<input type="text" />
</form>
);
}
}
簡単なフォームとインプットを作ります。
*クリックでもSubmitできるようにformタグを使っています。
applist.js
import React from 'react';
export class ToDoAppList extends React.Component {
constructor() {
super();
}
render() {
return (
<ul>
<li>Task 1</li>
<li>Task 2</li>
<li>Task 3</li>
</ul>
);
}
}
ただの平凡なリストを表示します。
Task 1、Task 2というように、ダミーでテキストを入れておきます。
とりあえず、何も動きのないフォームとリストを表示されるようになりました。
ここまででこんな感じで表示されるかと思います。
これをToDoアプリみたいにしていきます。
index.js
var tasksList = ["Task 1", "Task 2", "Task 3"];
ReactDOM.render(<Todo tasks={tasksList} />, document.getElementById('todo'));
registerServiceWorker();
var taskListで適当に配列を作り、それをToDoコンポーネントにtasks={tasksList}で渡します。
*これでToDoコンポーネント内でtasksプロパティを使えるようになります。
main.js
export class Todo extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask />
<ToDoAppList tasks={this.props.tasks} />
</div>
);
}
}
渡されたtasksプロパティをToDoAppListコンポーネントに渡します。
*props.tasksListではありません
applist.js
import React from 'react';
export class ToDoAppList extends React.Component {
constructor() {
super();
}
render() {
var items = this.props.tasks.map((elem, i) => {
return <li key={i}>{elem}</li>
});
return (
<ul>
{items}
</ul>
);
}
}
渡されたtasksプロパティをmapで配列を作ります。
ユニークキーkey={i}を設定していないとエラーが出ます。
itemsを{}で囲んでul内に入れます。
すると、index.jsで作った配列がこのulまで渡ってきます。
次に作った配列をstateに変えます。
main.js
import React from 'react';
import {AddNewTask} from './addtask';
import {ToDoAppList} from './applist'
export class Todo extends React.Component {
constructor(props) {
super();
this.state = {tasks: props.tasksList};
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask />
<ToDoAppList tasks={this.state.tasks} />
</div>
);
}
}
これでstateになりました。
簡単にいうと、stateは可変・propsは不変
つまり、後々変えることができるのがstateで、プロパティは一度セットされたらずっとそのままです。
addtask.js
import React from 'react';
export class AddNewTask extends React.Component {
constructor() {
super();
//justSubmitted内でthisを使っているのでbind
this.justSubmitted = this.justSubmitted.bind(this);
}
justSubmitted(event) {
//ページがロードされるのを防ぐ
event.preventDefault();
//ユーザーが入力したテキストを取得
var input = event.target.querySelector('input');
var value = input.value;
input.value = '';
//updateListをコール
this.props.updateList(value);
}
render() {
return (
//onSubmitでjustSubmittedを発火
<form onSubmit={this.justSubmitted}>
<input type="text" />
</form>
);
}
}
onSubmitでユーザーが入力したテキストを取得する
後々作るupdateListをコールしておく*後からでオケー
main.js
import React from 'react';
import {AddNewTask} from './addtask';
import {ToDoAppList} from './applist'
export class Todo extends React.Component {
constructor(props) {
super();
this.state = {tasks: props.tasks};
//updateList内でthisを使っているのでbindする
this.updateList = this.updateList.bind(this);
}
//リストの更新
updateList(text) {
var updatedTasks = this.state.tasks;
updatedTasks.push(text);
this.setState({tasks: updatedTasks}); //stateを更新
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask updateList={this.updateList} />
<ToDoAppList tasks={this.state.tasks} />
</div>
);
}
}
これで入力したテキストが追加されるようになります。
子コンポーネントから親コンポーネントへ入力されたデータを渡しています。
AddNewTasksコンポーネントにupdateList={this.updateList} />で更新されたstateを渡す。
こんな感じです。
次に追加したタスクの削除ボタンを作っていきます。
applist.js
import React from 'react';
export class ToDoAppList extends React.Component {
constructor() {
super();
this.remove = this.remove.bind(this);
}
remove(elem) {
var value = elem.target.parentNode.querySelector('span').innerText;
this.props.remove(value);
}
render() {
var items = this.props.tasks.map((elem, i) => {
return <li key={i}><span>{elem}</span><button onClick={this.remove}>X</button></li>
});
return (
<ul>
{items}
</ul>
);
}
}
buttonタグ内にXを入れてバツボタンみたいにしています。
めんどくさがりww
onClickでremoveのfunctionが発動するようにしています。
var valueについて、
elem.targetでthis.removeがある箇所のelemが基準となる
parentNodeでliタグへ
querySelector(’span’)でli内のspanへ
innerTextで{elem}のテキストへ
そしてそのテキストをthis.props.removeで削除します。
main.js
import React from 'react';
import {AddNewTask} from './addtask';
import {ToDoAppList} from './applist'
export class Todo extends React.Component {
constructor(props) {
super();
this.state = {tasks: props.tasks};
//updateList内でthisを使っているのでbindする
this.updateList = this.updateList.bind(this);
}
//リストの更新
updateList(text) {
var updatedTasks = this.state.tasks;
updatedTasks.push(text);
this.setState({tasks: updatedTasks});
}
//タスクの削除
removeTask(text) {
var updatedTasks = this.state.tasks;
updatedTasks.splice(updatedTasks.indexOf(text), 1);
this.setState({tasks: updatedTasks});
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask updateList={this.updateList} />
<ToDoAppList tasks={this.state.tasks} remove={this.removeTask} />
</div>
);
}
}
remove={this.removeTask}をToDoAppListに渡します。
removeTask内のupdatedTasks.splice(updatedTasks.indexOf(text), 1);について、
例:配列が[5, 4, 3, 2, 1]の時に4をremoveする*配列から1つremoveする
これでタスクの追加と削除ができるようになりました。
こんな感じ
このままだと、ページをリロードしたら元の状態に戻るので、ローカルストレージを使ってそこにタスクを格納していきます。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Todo} from './components/main'
import registerServiceWorker from './registerServiceWorker';
var tasksList = ["Task 1", "Task 2", "Task 3"];
//ローカルストレージにtasksがある場合、そのデータを引っ張ってくる
var tasks = localStorage.getItem('storedTasks');
//tasksがある場合
if(tasks) {
tasksList = JSON.parse(tasks);
}
ReactDOM.render(<Todo tasks={tasksList} />, document.getElementById('todo'));
registerServiceWorker();
main.js
import React from 'react';
import {AddNewTask} from './addtask';
import {ToDoAppList} from './applist'
export class Todo extends React.Component {
constructor(props) {
super();
this.state = {tasks: props.tasks};
//updateList/removeTask内でthisを使っているのでbindする
this.updateList = this.updateList.bind(this);
this.removeTask = this.removeTask.bind(this);
}
//リストの更新
updateList(text) {
var updatedTasks = this.state.tasks;
updatedTasks.push(text);
this.setState({tasks: updatedTasks});
//ローカルストレージ
this.updateLocalStorage(updatedTasks);
}
//タスクの削除
removeTask(text) {
var updatedTasks = this.state.tasks;
updatedTasks.splice(updatedTasks.indexOf(text), 1);
this.setState({tasks: updatedTasks});
//ローカルストレージ
this.updateLocalStorage(updatedTasks);
}
//ローカルストレージへデータを格納
updateLocalStorage(updatedTasks) {
localStorage.setItem('storedTasks', JSON.stringify(updatedTasks));
}
render() {
return (
<div>
<h1>Todo App</h1>
<AddNewTask updateList={this.updateList} />
<ToDoAppList tasks={this.state.tasks} remove={this.removeTask} />
</div>
);
}
}
*ローカルストレージではデータはstringで格納しないといけないのでstringify
これでローカルストレージにデータが格納されるので、リロードしても追加したタスクは消えずに残ります。
こんな感じ
また、追加した時にリストの一番下に追加されていくのが嫌な場合、
main.jsのリストの更新の箇所にある、updatedTasks.push(text);をupdatedTasks.unshift(text);にすると、一番上に追加されるようになります。
こんな感じ
以上になります。
何回か繰り返しやってみると、流れがわかってくるかと思います。
わからないところはググれば大抵出てきます。
コードはGithubに載せているので参考にしてみてください。
以上、ReactでTodoアプリを作った時の備忘録でした。