業務で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.children
かprops
として渡すか
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のボタンが効かなくなる。
num
と setflag
の値の処理が邪魔しあっているので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>
);
};
|
完成品