JavaScriptを有効にしてください

モダンJavaScriptの基礎から始める挫折しないためのReact入門

 ·   ·  ☕ 9 分で読めます  ·  🐯 ブシトラ

業務でReactを一瞬使ったが、体系的に学んでみたかったので購入。

モダンJavaScriptの基礎から始める挫折しないためのReact入門

※雑なメモとして残すが、自分以外には有益ではないと思います。

セクション2 環境について

CodeSandbox for vanilla JS

ホットリロードが効いてるので変更がすぐ反映される
基本は⌘Sする。
pretier使ってるので自動整形される
codesandboxとlinkできるの便利(create→fork→作業)

セクション3 ReactやVueを使う上で知っておきたいこと

従来はDOM操作だったので、レンダリングコストが高かった/複雑だった

仮想DOMの誕生。

かつてはすべてのJSに記載していたので、
パッケージマネージャが生まれた

ECMAスクリプト→ES2015(ES6)

トランスパイラ→新しい記法を古い記法に変換。IEとか。

セクション4 JS基本機能に触れる

基礎のため割愛

セクション5

VANILLA JS で TODOアプリ作成

完成品
空でもcreate出来るところとか細かいところはユルシテ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import "./styles.css";

const onClickAdd = () => {
  const inputText = document.getElementById("add-text").value;
  document.getElementById("add-text").value = "";
  createImcompletelist(inputText);
};

// 未完了リストから指定のリストを削除
const deleteFromImcompleteList = (target) => {
  document.getElementById("imcomplete-list").removeChild(target);
  // deleteTarget.remove();   だとよくなさそう?
};

// 未完了リストに追加する関数
const createImcompletelist = (text) => {
  // divを生成
  const div = document.createElement("div");
  div.className = "list-row";

  // liを作成
  const li = document.createElement("li");
  li.innerText = text;

  // 未完了のリストに追加
  document.getElementById("imcomplete-list").appendChild(div);

  // button(完了)タグ作成
  const completeButton = document.createElement("button");
  completeButton.innerText = "完了";
  completeButton.addEventListener("click", () => {
    deleteFromImcompleteList(completeButton.parentNode);

    const addTarget = completeButton.parentNode;

    // todo内容テキストを取得
    const text = addTarget.firstElementChild.innerText;

    // div以下を初期化
    addTarget.textContent = null;

    // liタグを作成
    const li = document.createElement("li");
    li.innerText = text;

    // 戻すボタンタグ設定
    const backButton = document.createElement("button");
    backButton.innerText = "戻す";
    backButton.addEventListener("click", () => {
      // 押された戻すボタンの親タグを完了リストから削除
      const deleteTarget = backButton.parentNode;
      document.getElementById("complete-list").removeChild(deleteTarget);

      // テキストを取得
      const text = backButton.parentNode.firstElementChild.innerText;
      createImcompletelist(text);
    });

    // divタグの子要素に各要素を設定
    addTarget.appendChild(li);
    addTarget.appendChild(backButton);

    // 完了したリストに追加
    document.getElementById("complete-list").appendChild(addTarget);
  });

  // button(終了)タグ作成
  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";
  deleteButton.addEventListener("click", () => {
    deleteFromImcompleteList(deleteButton.parentNode);
  });

  div.appendChild(li);
  div.appendChild(completeButton);
  div.appendChild(deleteButton);
};

document
  .getElementById("add-button")
  .addEventListener("click", () => onClickAdd());

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
  <head>
    <title>Todo(JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div class="input-area">
      <input id="add-text" placeholder="todoを入力" />
      <button id="add-button">追加</button>
    </div>
    <div class="incomplete-area">
      <p class="title">未完了のtodo</p>
      <ul id="imcomplete-list"></ul>
    </div>
    <div class="complete-area">
      <p class="title">完了したtodo</p>
      <ul id="complete-list"></ul>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
body {
  font-family: sans-serif;
}

input {
  border-radius: 16px;
  border: none;
  padding: 6px 16px;
  outline: none; /* focusした時に枠がでない */
}

button {
  border-radius: 16px;
  border: none;
  padding: 6px 16px;
  margin-right: 5px;
}

button:hover {
  background-color: #ff7fff;
  color: #fff;
  cursor: pointer;
}

li {
  margin-right: 8px;
}

.input-area {
  background-color: #c1ffff;
  width: 400px;
  height: 30px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.complete-area {
  background-color: #ffffe0;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.incomplete-area {
  background-color: #c6ffe2;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.title {
  text-align: center;
  margin-top: 0px;
  font-weight: bold;
  color: #666;
}

.list-row {
  display: flex; /* 横並び */
  align-items: center;
  padding-bottom: 4px;
}

HTMLとCSSについては特に言うことはない。idとclassを採番したくらい。
JSでTODOアプリ作ると、親とか子供の要素指定リレーが結構たいへん。
TODOアプリでこうだから、業務レベルで生JSのみで書いたら死ぬ。

・parentNode → 指定されたノードの DOM ツリー内の親ノードを返します。
・textContent →ノードおよびその子孫のテキストの内容を表します。
・innerText → ノードとその子孫の「レンダリングされた」テキスト内容を示します

あたりはちょっと調べた。

セクション6 Reactの基本

・JSXは<div>で囲まないとエラーになる。
・コンポーネントはPascalCaseにすること。
・変数等は、camelCaseにすること。
もしくは、<React.Fragment><>囲む。(divを使いたくない場合)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";

const App = () => {

  const onClickButton = () => alert();
  const contentStyle = {
    color: 'blue',
    fontSize: '18px'
  };

  return (
    <React.Fragment>
      <h1 style={{color: 'red'}}>hello</h1>
      {/* ={{}} で囲む redもstringとして囲む */}
      <p style={contentStyle}>good</p>
      {/* 定義してからも可能 */}
      <button onClick={onClickButton}>ボタン</button>
    </React.Fragment>
  )
}
export default App;

props

props.childrenpropsとして渡すか

useState

1
2
3
4
5
const [num, setNum] = useState(0);

const onClickCountUp = () => {
  setNum(num + 1)
}

上記のように設定して、関数をトリガーにsetNum等で変更する。

再レンダリング

Reactのコンポーネントが再レンダリングされるので、差分を表示出来る。

・propsの中身が変わった時、
・stateを変更した時
・親のコンポーネントが再レンダリングされた時、子も再レンダリングされる

Too many re-rendersなど
無限レンダリングをはらむ可能性あるコードは気をつける。レンダリングコストを考える。

useEffect

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState } from "react";

const App = () => {

  const [num, setNum] = useState(0);
  const [showFlag, setShowFlag] = useState(true);

  const onClickCountUp = () => {
    setNum(num + 1);
  };

  const onClickSwitchShowFlag = () => {
    setShowFlag(!showFlag);
  };

  if (num > 0) {
    if (num % 3 === 0) {
      showFlag || setShowFlag(true);
      // ここがあるのでnumが変わってもボタンは変わらない
    } else {
      showFlag && setShowFlag(false);
    }
  }

  return (
    <React.Fragment>
      <button onClick={onClickCountUp}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickSwitchShowFlag}>on/off!</button>
      {showFlag && <p>(´ε )</p>}
    </React.Fragment>
  )
}

export default App;

上記だと、on/offのボタンが効かなくなる。
numsetflag の値の処理が邪魔しあっているのでflagがレンダリングされず動かない。

そこで関心の分離をする。

1
2
useEffect(() => {
}, []);

第2引数が空の場合初期レンダリングのみ通る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
useEffect(() => {
  console.log("num is changed");
  if (num > 0) {
    if (num % 3 === 0) {
      showFlag || setShowFlag(true);
    } else {
      showFlag && setShowFlag(false);
    }
  }
}, [num]);

上記のようにすれば、showFlagのstateの値に関心を持たないため、on/offが効くようになる。
ただ、第2引数にeffect内のshowFlag を使ってるのでlintはエラーになるのでそこは注意(PJ毎にどう管理するかはおまかせ)

完成品

セクション7 React Todo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

import React, { useState } from "react";
import "./styles.css";

export const App = () => {

  const [todoText , setTodoText] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);  //inputがchangeする度に変わる
  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  }

  const onClickDelete = (idx) => {
    const newTodos = [...incompleteTodos]
    newTodos.splice(idx, 1);
    setIncompleteTodos(newTodos)
  };

  const onClickComplete = (idx) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(idx, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[idx]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);

    // 未完了から削除して、完了に一つ足す
  };

  const onClickBack = (idx) => {
    const newCompleteTodos = [...completeTodos];
    console.log(newCompleteTodos);
    newCompleteTodos.splice(idx, 1)

    const newIncompleteTodos = [...incompleteTodos, completeTodos[idx]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };



  return (
    <>
      <div className="input-area">
        <input
          placeholder="todoを入力"
          value={todoText}
          onChange={onChangeTodoText}
         />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のtodo</p>
        <ul>
          {incompleteTodos.map((todo, idx) => {
            return (
              <div key={todo} className="list-row">
                {/* mapなどレンダリングした時はkeyがいる */}
              <li>{todo}</li>
              <button onClick={() => onClickComplete(idx)}>完了</button>
              <button onClick={() => onClickDelete(idx)}>削除</button>
              {/* 関数に引数を渡す時はアロー関数で囲まないと即時実行される */}
            </div>
            )
          })}
        </ul>
      </div>
      <div className="complete-area">
      <p className="title">完了のtodo</p>
        <ul>
          {completeTodos.map((todo, idx) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickBack(idx)}>戻す</button>
              </div>
            )
          })}
        </ul>
      </div>
    </>
  );
};

コンポーネント分割後

Todoレベルではコンポーネント分割する意味は抽象度的に意味ないと思うが、いい復習になりました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React, { useState } from "react";
import "./styles.css";
import { InputTodo } from "./components/InputTodo";
import { IncompleteTodos } from "./components/IncompleteTodos";
import { CompleteTodos } from "./components/CompleteTodos";

export const App = () => {

  const [todoText , setTodoText] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);  //inputがchangeする度に変わる
  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  }

  const onClickDelete = (idx) => {
    const newTodos = [...incompleteTodos]
    newTodos.splice(idx, 1);
    setIncompleteTodos(newTodos)
  };

  const onClickComplete = (idx) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(idx, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[idx]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);

    // 未完了から削除して、完了に一つ足す
  };

  const onClickBack = (idx) => {
    const newCompleteTodos = [...completeTodos];
    console.log(newCompleteTodos);
    newCompleteTodos.splice(idx, 1)

    const newIncompleteTodos = [...incompleteTodos, completeTodos[idx]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

  return (
    <>
      <InputTodo
        todoText={todoText}
        onChange={onChangeTodoText}
        onClick={onClickAdd}
      />
      <IncompleteTodos
        todos={incompleteTodos}
        onClickComplete={onClickComplete}
        onClickDelete={onClickDelete}
      />
      <CompleteTodos
        todos={completeTodos}
        onClickBack={onClickBack}
      />
    </>
  );
};

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import React from "react";

export const InputTodo = (props) => {
  const { todoText, onChange, onClick} = props;

  return  (
    <div className="input-area">
      <input
        placeholder="todoを入力"
        value={todoText}
        onChange={onChange}
      />
      <button onClick={onClick}>追加</button>
    </div>
  );
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from "react";

export const IncompleteTodos = (props) => {
  const { todos, onClickComplete, onClickDelete } = props;

  return (
    <div className="incomplete-area">
      <p className="title">未完了のtodo</p>
      <ul>
        {todos.map((todo, idx) => {
          return (
            <div key={todo} className="list-row">
                {/* mapなどレンダリングした時はkeyがいる */}
              <li>{todo}</li>
              <button onClick={() => onClickComplete(idx)}>完了</button>
              <button onClick={() => onClickDelete(idx)}>削除</button>
              {/* 関数に引数を渡す時はアロー関数で囲まないと即時実行される */}
            </div>
          )
        })};
      </ul>
    </div>
  );
};

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";

export const CompleteTodos = (props) => {
  const { todos, onClickBack } = props;

  return (
    <div className="complete-area">
      <p className="title">完了のtodo</p>
        <ul>
          {todos.map((todo, idx) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickBack(idx)}>戻す</button>
              </div>
            )
          })}
        </ul>
    </div>
  );
};

完成品

共有

busitora
著者
ブシトラ
エンジニア