React.jsでTodoアプリ【初心者向け】備忘録メモ

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.targetthis.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アプリを作った時の備忘録でした。



Posted

in