CDNでReact入門

# CDNでReact入門
Reactは、JavaScriptWebフレームワークの中で一番人気である。しかし、こちらを扱うには、Nodejsの環境を作る必要があり、ハードルが高い。
ところが、CDNを使えば、環境構築をしなくても、お試しでReactが使える。
今回で、Reactの良さを色んな人に味わって欲しい。

## 前提条件 
- HTML/CSSがわかる
- JavaScriptの最新バージョンのコードがわかる
- 分割代入がわかる
- Visual Studio Codeが使える
- LiveServerをインストールしている
- Prettierをインストールし、セットアップも完了している。(推奨)

## プロジェクト作成
例によってTodoListを作る。
CLI使用
```
mkdir react-todolist
cd react-todolist
```
Linux系
```
touch index.html
touch style.css
```
PowerShell
```
New-Item index.html
New-Item style.css
```
## 初期化
```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>React Todo List</title>
  </head>
  <body>
    <div id="root"></div>
    <script
      src="https://unpkg.com/react@18/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <script type="text/babel">
      const App = () => {
        return (
          <div>
            <h1>Todo List</h1>
          </div>
        );
      };

      const container = document.getElementById("root");
      const root = ReactDOM.createRoot(container);
      root.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>
      );
    </script>
  </body>
</html>

```
```css:style.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  text-decoration: none;
}

body {
  width: 100%;
  height: 100vh;
}

#root {
  display: flex;
  justify-content: center;
  align-items: center;
}

h1 {
  font-size: 32px;
}

```
:::note
補完機能が使えるように、htmlの中に直接JavaScriptを書いている。
:::

この状態で、LiveServerを起動すると、
![init-image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3692952/a300ea88-3d0b-d9b4-8ef6-21ff0e3aba5e.png)
このようになっているはず。

```jsx:index.html
// Appコンポーネントという。
const App = () => {
    return (
        <div>
            <h1>Todo List</h1>
        </div>
    );
};
```
この書き方は、htmlではなく、JSX記法と呼ばれており、
htmlレイアウトを返す関数をコンポーネントという。
このコンポーネントの中には**JavaScriptの式を埋め込むことができる。**
例えば、今のコードを次のように変えても、同じ結果となる。
```jsx:index.html
const App = () => {
    const title = "Todo List";
    return (
        <div>
            <h1>{title}</h1>
        </div>
    );
};
```
「{JavaScriptの式}」で、画面に変数や式を埋め込むことができる。

## 実際にタスクを入力するフォームを作る。

Appコンポーネントを以下のように変えてみてほしい。
```jsx:index.html
const App = () => {
    const title = "Todo List";
    let task = "";
    return (
        <div>
            <h1>{title}</h1>
            <input type="text" value={task} />
            <div id="displayTask">{task}</div>
        </div>
    );
};

```
一見うまくいきそうに見えるが、実際は、inputボックスに何も入力できない。
その上、コンソールでみても、何も変わっていない。
このような、ユーザーの行動によって変わる値を管理することを状態管理という。

Reactでは、そういう難しいことをhooksと呼ばれるものに任せる。
[Hooksについてもっと知りたい方はこちら](https://udemy.benesse.co.jp/development/react-hooks.html)
状態管理をするには、useState()と呼ばれるhooksを使う。
let task = "";を、以下のように変えよう。
```jsx:index.html
const [task, setTask] = React.useState("");
```
taskのことを、状態変数と呼び、この値は、setTask関数により変更できる。
例えば、setTask("あああ")とすると、taskの値が「あああ」となる。
**set関数の引数の値に変更されるのだ。**
これを踏まえて、inputボックスの値を取得しよう。
```jsx:index.html
<input type="text" onChange={(e) => setTask(e.target.value)} value={task} />
```
ここで、「e」という引数が出てきたが、これは、addEventListenerを使うときの「e」だと思ってもらいたい。e.target.valueには、input要素のvalue属性が格納されてる。
<details>
    <summary>e.targetの補足</summary>
    コンソールで、e.targetを見ると、inputタグが格納されている。
    <br />
    hoverすると、自分が入力したinputボックスがハイライトされるはずだ。
</details>

### hooksの呼び出しを簡単にする。
ここで、hooksは、今後何度も登場し、いちいちReact.としてアクセスするのは面倒くさいので**分割代入**で取り出すことにする。
scriptタグの真下に以下のコードを足そう。
```jsx:index.html
const { useState } = React;
```
これによって、taskを定義する部分は、これでも正常に動く。
```jsx:index.html
const [task, setTask] = useState("");
```

## TodoListの状態を管理する。
TodoListは、1個や2個の世界ではなく、無限に追加できるため、配列によって管理する。
そのため、Appコンポーネントの一番上に、todosを自分で定義しよう。
<details><summary>解答例</summary>

```jsx:index.html
// 初期値は、状態変数の型によって変わる。
// 今回は配列を使いたいので、空の配列になっている。
const [todos, setTodos] = useState([]);
```

</details>

配列の中にTodoを入れたいが、**Todoの情報は、タスクだけではない。**
今回は、完了・未完了を与える。
そのため、Todoはオブジェクトで管理する。
todosを以下のように変更しよう。
```jsx:index.html
const [todos, setTodos] = useState([
    { task: "散歩", checked: false },
    { task: "ゲーム", checked: false }
])
```
### 画面にTodoを表示する。
todosをすべて表示したいときはmap関数を使って値を取り出して、表示する。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

```jsx:index.html
{/* <div id="displayTask">{task}</div> */}
<ul>
    {todos.map((todo) => (
        <li>
            <input
                type="checkbox" 
                checked={todo.checked} 
            />
            <p>{todo.task}</p>
        </li>
    ))}
</ul>
```
:::note
```jsx:index.html
{todos.map(({ task, checked }) => (
        <li>
            <input
                type="checkbox" 
                checked={checked} 
            />
            <p>{task}</p>
        </li>
))}
```
のように、分割代入でも取り出せる。
:::

ただし、このままでは、タスクが被った場合、見分けがつかなくなってしまう
そこで、Todo一つひとつに**idを付与する必要がある**
idは今後タスクが増えることが予想されるため、
ランダムな文字列にしよう。
Appコンポーネントの外に以下の関数を定義してほしい。
```jsx:index.html
const genId = () => {
    const chars = [..."abcdefghijklmnopqrstuvwxyz1234567890"];
    let id = "";
    for(let i = 0; i < 32; i++) {
        id += chars[Math.floor(Math.random() * chars.length)];
    }
    return id;
}
```
:::note
npmには、uuidというユニークなidを付与するライブラリが存在する。
:::
そしたら、Todoにidを付与しよう。
```jsx:index.html
const [todos, setTodos] = useState([
    { id: genId(), task: "散歩", checked: false },
    { id: genId(), task: "ゲーム", checked: false }
])
```
todosにidを付与したら、liタグに、key={todo.id}を足してほしい。

## レイアウトをTodoListに近づける。
TodoListぽくするために、Appコンポーネントのレイアウトを大幅に変える。
```jsx:index.html
return (
    <div>
        <h1>{title}</h1>
        <div id="task">
            <label>タスク：</label>
                <input
                    type="text"
                    value={task}
                    onChange={(e) => setTask(e.target.value)}
                />
            <button id="add">タスクの追加</button>
        </div>
        
        <ul>
            {todos.map(({ id, task, checked }) => (
                <li key={id}>
                    <input type="checkbox" checked={checked} />
                    <p>{task}</p>
                    <button>削除</button>
                </li>
            ))}
        </ul>
    </div>
);
```

これだと、liタグの中が縦長なのはアレなのでflexを当てたい。
そこで、liに「todo」というクラスを当てよう。
```jsx:index.html
<li key={id} class="todo"></li>
```
としたいところだが、Javascriptでclassが予約語なので、JSXでは**className**という単語を使う。
```jsx:index.html
<li key={id} className="todo"></li>
```
```css:style.css
.todo {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 10px;
}

.todo * {
    padding: 2px;
    margin: 5px;
}
```

これで、CSSが当たるはずである。

## タスクの追加
まずは、タスクの追加から実装していこう。
流れとしては、
inputの取得 → todosに追加 → 表示 → inputボックスをクリアー
となる。

**inputの取得 → todosに追加 → 表示** → inputボックスをクリアー
ここを一気にやりましょう。

タスクを追加する関数を定義する。
setTodos()の引数に、todosが更新されるので、非破壊的に追加しなければならない。
そのため、スプレッド構文を用いる。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

```jsx:index.html
const addTodo = () => {
    setTodos([...todos, {
        id: genId(),
        task,
        checked: false
    }])
}
```
inputの取得 → todosに追加 → 表示 → **inputボックスをクリアー**
では、今の太字の部分を実装してみよう。
<details><summary>解答例</summary>

```diff_jsx:index.html
// taskの値を空の文字列にすればよい。
const addTodo = () => {
    setTodos([...todos, {
        id: genId(),
        task,
        checked: false
    }])
+  setTask("");
}
```

</details>

このままでもいいのだが、inputテキストの値をuseStateで管理すると、**入力するたびに、画面が書き換えられてしまう**ためパフォーマンスが悪い。
なので、useRefという、別のHooksを使用する。
Hooksを取り出す部分にuseRefを追加しよう。
```jsx:index.html
const { useState, useRef } = React;
```
このuseRefは、取得したい要素にref属性を付与して使う。
inputタグを以下のように変えてほしい

```diff_jsx:index.html
<input
    type="text"
-   value={task}
-   onChange={(e) => setTask(e.target.value)}
+   ref={taskRef}
/>
```
taskRefは、taskの状態変数を定義している部分を置き換える。
```diff_jsx:index.html
- const [task, setTask] = useState("");
+ const taskRef = useRef();
```

addTodo関数では、refからタスクを取得する処理に変える。
ついでに空のタスクを追加できないようする。

```diff_jsx:index.html
const addTodo = () => {
+   const task = taskRef.current.value;
+   const msg = "タスクを入力してください。";
+   if (task === "") {
+       taskRef.current.placeholder = msg;
+       throw new Error(msg);
+   }
   
    setTodos([...todos, {
        id: genId(),
        task,
        checked: false
    }]);
    
-  setTask("");
+  taskRef.current.value = "";
}
```
:::note
currentの中に、querySelectorとかで取れるオブジェクトが入っている。
:::

## チェックボックスの実装
チェックボックスの切り替えは、toggle関数を作って、実装していく。
流れは、
todosをコピーする。→ idから、変えたいチェックボックスを取得する。 → checkedを反転させる。 → 状態を更新して表示する。

**idから、変えたいチェックボックスを取得する。** には、配列のfindメソッドを利用する。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find

addTodoの下に定義しよう。

```jsx:index.html
const toggle = (id) => {
    // todosをコピーする
    const newTodos = [...todos];
    // idから変えたいチェックボックスを取得する。
    const todo = newTodos.find(
        todo => todo.id === id // 条件がtrueになる一番最初の要素を取得。
    );
    // checkedを反転させる。
    todo.checked = !todo.checked;
    // 状態を更新して表示する。
    setTodos(newTodos);
};
```

そしたら、自力で、チェックボックスにtoggle関数を与えてみよう。

<details><summary>解答例</summary>

```jsx:index.html
todos.map(({ id, task, checked }) => (
    // 省略...
    <input
        type="checkbox"
        checked={checked}
        onChange={() => toggle(id)}
    />
));
```
</details>

checkedがtrueの時は、タスクに取り消し線を入れ、テキストをグレーに変えよう。
三項演算子がわかれば、自力で実装できると思うので、挑戦してみてほしい。

<details><summary>解答例</summary>

```jsx:index.html
<li key={id} className={`todo ${checked ? "delline" : ""}`}>
    {/* 省略 */}
</li>
```

```css:style.css
.delline *:not(button) {
    text-decoration: line-through;
    color: grey;
}
```

</details>

## 削除の実装
remove関数を作って実装する。
流れ
todosをコピー。 → フィルタリングする → 状態を更新して表示。
フィルタリングは、filterメソッドを使うとよい。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

```jsx:index.html
const remove = (id) => {
    // todosをコピー。→ フィルタリングする
    const filteredTodos = [...todos].filter(
        // 引数todoには、todosの要素一つ一つが入っている。
        todo => todo.id !== id // 条件がtrueになるすべて要素の配列が返される。
    );
    // 状態を更新して表示。
    setTodos(filteredTodos);
    
}
```

### 軽く修正
今のままでは、このサイトを開いた瞬間、散歩とゲームをすることが確定してしまうので
todosの初期値を空の配列にしておこう。
```jsx:index.html
const [todos, setTodos] = useState([]);
```

## やってみよう1： 残りのタスクを表示する。
配列のfilterメソッドを使えば実装できる。
<details><summary>コードの一例</summary>

```jsx:index.html
<div>残りのタスク: {todos.filter(({ checked }) => !checked).length}</div>
```

</details>

## やってみよう2： 完了したタスクをまとめて削除する。
こちらも、filterメソッドで実装しよう。
一行で書けるので、buttonタグの中に直接、無名関数を入れてしまおう。

<details><summary>解答</summary>

```jsx:index.html
<button
    onClick={
        () => setTodos([...todos].filter(({ checked }) => !checked))
    }
>
    完了したタスクを削除
</button>
```
</details>

これで、機能は十分だと思う。

## 一つ上の段階

このままで完成でもいいが、コードが見ずらいかもしれない。
現Appコンポーネントをここに示す。
```jsx:index.html
const App = () => {
    const title = "Todo List";
    const taskRef = useRef();
    const [todos, setTodos] = useState([]);

    // タスクを追加する。
    const addTodo = () => {
        // inputテキストの取得。
        const task = taskRef.current.value;
        const msg = "タスクを入力してください。";
        
        if (task === "") {
            taskRef.current.placeholder = msg;
            throw new Error(msg);
        }
        
        taskRef.current.placeholder = "";
        setTodos([
            ...todos,
            {
                id: genId(),
                task,
                checked: false,
            },
        ]);
        taskRef.current.value = "";
    };
    
    const toggle = (id) => {
        // todosをコピーする
        const newTodos = [...todos];
        // idから変えたいチェックボックスを取得する。
        const todo = newTodos.find((todo) => todo.id === id);
        // checkedを反転させる。
        todo.checked = !todo.checked;
        // 状態を更新して表示する。
        setTodos(newTodos);
    };
    
    const remove = (id) => {
        const newTodos = [...todos].filter((todo) => todo.id !== id);
        setTodos(newTodos);
    };
    
    return (
        <div>
            <h1>{title}</h1>
            <div id='task'>
                <label>タスク：</label>
                <input type='text' ref={taskRef} />
                <button id='add' onClick={addTodo}>
                タスクの追加
                </button>
            </div>
            
            <div>
                残りのタスク: {[...todos].filter(({ checked }) => !checked).length}
            </div>
            <button
                onClick={() =>
                    setTodos([...todos].filter(({ checked }) => !checked))
                }>
                完了したタスクを削除
            </button>
        
            <ul>
                {todos.map(({ id, task, checked }) => (
                    <li key={id} className={`todo ${checked ? "delline" : ""}`}>
                        <input
                            type='checkbox'
                            checked={checked}
                            onChange={() => toggle(id)}
                        />
                        <p>{task}</p>
                        <button onClick={() => remove(id)}>削除</button>
                    </li>
                ))}
            </ul>
        </div>
    );
};
```

Reactには、コードを見やすくしたり、チームで開発しやすくしたりするために、
**子コンポーネント**という概念がある。
コンポーネントは、本来別ファイルで管理するが、CDNの環境のため、importなどの処理が面倒になることから、同じファイル内に二つのコンポーネントを定義する。
Appのしたに、TodoListコンポーネントを定義する。

```jsx:index.html
const TodoList = () => {
    return;
}
```

TodoListのレイアウトは、ulタグを切り取って、貼り付けよう
toggleとremoveは、TodoListでしか使わないので、これも切り取って貼り付けよう。

```jsx:index.html
const TodoList = () => {
   const toggle = (id) => {
      // todosをコピーする
      const newTodos = [...todos];
      // idから変えたいチェックボックスを取得する。
      const todo = newTodos.find((todo) => todo.id === id);
      // checkedを反転させる。
      todo.checked = !todo.checked;
      // 状態を更新して表示する。
      setTodos(newTodos);
    };

    const remove = (id) => {
      const newTodos = [...todos].filter((todo) => todo.id !== id);
      setTodos(newTodos);
    };
    
    return (
        <ul>
            {todos.map(({ id, task, checked }) => (
              <li key={id} className={`todo ${checked ? "delline" : ""}`}>
                <input
                  type='checkbox'
                  checked={checked}
                  onChange={() => toggle(id)}
                />
                <p>{task}</p>
                <button onClick={() => remove(id)}>削除</button>
              </li>
            ))}
        </ul>
    );
}
```

さて、問題は、[todos, setTodos]である。

## AppにTodoListを表示する。

AppにTodoListを表示するには、TodoListタグのような形をとる。
```diff_jsx:index.html
const App = () => {
    return (
        // 割愛
-       <ul>{/* 省略 */}</ul>
        <TodoList />
    );
};
```

## propsで値を渡す

コンポーネントの引数に、{ some }を入れると、HTMLの属性のような形で値を受け取れる。
これは、見たほうが早い。

```jsx:index.html
const TodoList = ({ todos, setTodos }) => { // 割愛

const App = () => {
    // 割愛
    <TodoList todos={todos} setTodos={setTodos} />
}
```
:::note
勘づいた方もいると思うが、propsは、分割代入で取り出さている。
なので、const TodoList = props => props.todos.map // 省略
のように取り出すこともできる。これは、好みである。
:::

## 最後に
これで**Todoリストは99%完成した！**

1%の課題は、**リロードしたら、タスクが消えること。**
この課題の解答例は、別の記事に書こうかと思う。
ヒントは、**ローカルストレージ**だ。

----
**最後まで読んでいただきありがとうございました。**
公式ドキュメントはこちらから。

https://ja.react.dev/