UdemyでReactを勉強する日記
frontend真面目にやったことないので基礎からちゃんとやる。
ref: https://www.udemy.com/course/react-the-complete-guide-incl-redux/learn/lecture/25595350#overview
現在地
あなたの現在地 by ChatGPT
おそらく知識としては
jQuery
↓
gulp/grunt
↓
Vueを少し触った
↓
React少し触った
↓
Next.jsのJSXを修正するくらい。
今のReact開発者の感覚だと
React
↓
State
↓
Hooks
↓
Router
↓
SSR
↓
SSG
↓
ISR
↓
Server Components
↓
Next.jsなので 途中がかなり抜けています。
ということです。
さ、やろう
Section 2
import & <script type="module"
jQuery時代にはなかったimport. Javascript fileからJavascript fileを呼び出せる。
流れは html -> (load) -> js1 <- (inject) js2 という感じ?
まず js2 をこうしておく
export function aaa(){
alert(1);
}
それをjs1で波カッコつけて読み込む。
import { aaa } from "./2.js";
// call
aaa();
importしてる方を HTMLで type="module" をつけて読み込むと使える。
x <script src="1.js"></script>
o <script src="1.js" type="module"></script>
昔ながらの type=module なしで読み込むと、importは使えない。
❌ Uncaught SyntaxError: Cannot use import statement outside a module (at app.js:1:1)
当時本当にこれ欲しかったな!!!
export default
export const password = "abc123";
export default "default-value";
default は波カッコなしで import する。
import thingamabob, { password } from "./topics.js";
^^^^^^^^^^^
console.log(password); // Output: abc123
console.log(thingamabob); // Output: default-value
波カッコを忘れると、defaultを読んでしまうので注意!!
import password from "./topics.js"; // default_valueが入ってる!!
as なしで自由に名前つけれる default は危険すぎると思う
objectとしてまとめてimport
import * as util from "./topics.js";
console.log(util.password); // Output: abc123
console.log(util.thingamabob); // Output: default-value
Destructuring
object: colonで名前がつけれる。
function: {} を引数のとこに入れるとobjectから取り出せる。
// list
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// object
const { name, age } = { name: 'Alice', age: 30 };
console.log(name, age); // Alice 30
// object with renaming
const { name: userName, age: userAge } = { name: 'Bob', age: 25 };
console.log(userName, userAge); // Bob 25
// function parameters
function greet({ name, age }) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
greet({ name: 'Charlie', age: 35 }); // Hello, my name is Charlie and I am 35 years old.
Spreading
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]
const obj1 = { name: 'Alice', age: 30 };
const obj2 = { ...obj1, city: 'New York' };
console.log(obj2); // { name: 'Alice', age: 30, city: 'New York' }
めちゃめちゃやばい JSX の import
画像もcssもimportできちゃう。めっちゃやばい。
import pngFile from "./assets/react-core-concepts.png";
import "./index2.css";
function App() {
return (
<div>
<img src={pngFile} alt="Stylized atom" />
この時の main jsはこう
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
const entryPoint = document.getElementById("root");
ReactDOM.createRoot(entryPoint).render(<App />);
こんなやばいことができるのはviteのおかげ。
はい、ざっくり言うと Vite は web server でもあり、transpiler 的な処理もするツールです。
JSX の変換などは主に esbuild や React plugin が担当します。なので Vite は「transpilerそのもの」というより、transpiler や bundler を束ねて、ブラウザに必要な形で返す開発サーバーという理解が近いです。
不思議な変換pluginが入ったweb serverという理解か
vite
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
});
viteには「React プロジェクトとして扱ってね」という最低限のヒントは入っています。あとは Vite 側がかなり賢く既定動作を持っています。
あまりに不思議なので別記事にした。
props.children
不思議なreserved word children を覚えた
function App() {
return (
<div>
<Card name="Card 1">
<menu>
<button>aaa</button>
<button>bbb</button>
</menu>
</Card>
</div>
);
この Card の中身のhtml tagをpropsで渡したい
function Card(props) {
return (
<div>
<h1>
{props.name}
</h1>
{props.children} // <--------------
</div>
)
}
props.children に不思議な力で入ってくる。
props を {} で渡す
こうやって渡すと propsをkeyで取り出せる
function Card({name, children}) {
return (
<div>
<h1>
{name}
</h1>
{children}
</div>
)
}
props に anonymous function を渡す
定義するのは無名関数なので、無名関数が実行された時には中の関数が実行されるので、 そっちの () が必要
function handleSelect(selectedButton) {
console.log(selectedButton);
}
function TabButton({ mySelectedFunc, children }) {
return (
<button onClick={mySelectedFunc}>
{children}
</button>
)
}
function App() {
return (
<menu>
<TabButton mySelectedFunc={() => handleSelect('aaa')}>AAA</TabButton>
<TabButton mySelectedFunc={() => handleSelect('bbb')}>BBB</TabButton>
<TabButton mySelectedFunc={() => handleSelect('ccc')}>CCC</TabButton>
</menu>
);
}
useState
ここからがreactだ!
use から始まるのは全部 React Hooks. useState とか全部 React Hooks!!
Hooksはcomponentの中でしか書けない!
component functionのtop levelにしか書けない!! (nexted functionはだめ)
# WRONG!!
---
const [] = useState(); <--- x
function App() {
---
function App() {
if ()
const [] = useState(); <--- x
---
# GOOD!!
function App() {
const [] = useState(); <------ o
...
}
hookの値が変わると、componentはre-renderingされる
/**
title = current value
setTitle = updater
'I lean on you' = defalut value
*/
const [title, setTitle] = useState('I lean on you');
import { useState } from "react";
const title = "React Essentials ddd";
function App() {
const [selectedTopic, setSelectedTopic] = useState('Please clidk a button');
function handleSelected(selectedButton) {
setSelectedTopic(selectedButton);
}
return (
<div>
<main>
<h2>Time to get started!</h2>
<menu>
<button onClick={() => handleSelected('Components')}>Components</button>
<button onClick={() => handleSelected('State')}>State</button>
<button onClick={() => handleSelected('Props')}>Props</button>
</menu>
<section>
<h3>{selectedTopic}</h3>
</section>
</main>
</div>
);
}
export default App;
jsx if shorthand
この2つは同じ意味
{!selectedTopic ? <p>Please select a topic to view its details.</p> : null}
{!selectedTopic && <p>Please select a topic to view its details.</p>}
{} のなかにjsxも書ける。() でくくる
{selectedTopic && (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>{EXAMPLES[selectedTopic].code}</pre>
</div>
)}
jsx {}
これでも動く
{"hello"}
{["hello", "world"]}
{[<p>hello</p>, <p>world</p>]}
これは動かない
{hello}
変数helloがあれば動く
compoenent props の渡し方
こういうcomponentがあるとき
function CoreConcept({ image, title, description }) {
こういデータがあり
export const CORE_CONCEPTS = [
{
image: componentsImg,
title: 'Components',
description:
'The core UI building block - compose the user interface by combining multiple components.',
},
jsxはこう渡す
{CORE_CONCEPTS.map((item) => (
<CoreConcept {...item} key={item.title} />
))}
{...item} は展開されて image={item.image} title={item.title} description={item.description} と同じ意味になります。
、map() で配列を描画するときは key が必要です。React は key を使って要素の変更を効率的に追跡します。key は CoreConcept の props ではなく、React の内部用の識別子。ないとwarningが出る!
ここら辺で、useStateの値が変わった時にcomponentがre-renderingされる、つまり、js fileが上から下にもう一度読み込まれることに気づいた。これはでかい。child componentsも同様に読み込まれるから、getRandomInt()とかあると、それも実行されてrenderingされたりする。
されたくなかったら、変わるところをchild componentに分離する。親はレンダリングされないので、親から分離すれば、子供の世界だけレンダリングされる。
Fragment
root objectの代わり。vueの <> と同じぽい
return (
<Fragment>
<p>
<p>
</Fragment>
React JSX メモ
↓でいう JSX っていうのは html tagぽいもののこと。
JSXとは
HTMLっぽい記法だが、実際は JavaScript に変換される。
<div>Hello</div>
{} の意味
JSXの中で JavaScript を書く場所。
<h1>{name}</h1>
<p>{1 + 2}</p>
{isLoggedIn ? <User /> : <Login />}
return (...) の中は?
return の後は JavaScript の式を書く場所。
return (
richText ? <textarea /> : <input />
);
なので {} は不要。
JSXの中なら {} が必要
return (
<div>
{richText ? <textarea /> : <input />}
</div>
);
-
<div>...</div>→ JSX -
{ ... }→ JavaScript
{} の中でできること
✅ 式 (expression)
{1 + 2}
{user.name}
{isAdmin && <AdminBadge />}
{items.map(item => <li>{item}</li>)}
{} の中でできないこと
❌ 文 (statement)
{
const x = 1; // NG
}
{
if (x) {} // NG
}
実務での考え方
- JSXの中にJSを書くときだけ
{}を使う - 長いロジックは
returnの前に出す
const content = isLoggedIn
? <User />
: <Login />;
return <div>{content}</div>;
覚え方
JSXの中
↓
<div>
{JavaScript}
</div>
JavaScriptの中
↓
return (
JavaScript式
)
propsにjsxを渡してもいい
状態をpropsでたくさん渡すより、JSXそのものを渡すのもreactではいいパターンらしい。複雑な条件を持てないと動けないcomponentはよろしくない。
<Tabs
buttons={
<>
<TabButton
isSelected={selectedTopic === "components"}
onClick={() => handleSelect("components")}
>
export default function Tabs({ children, buttons }) {
return (
<>
<menu>{buttons}</menu>
{children}
</>
);
}
大文字から始まるとcomponentとして読まれる
html tag nameをpropsで渡す
<Tabs
buttonsContainer="menu"
それを作りたい
小文字のままだとcomponent(aka html tag) として認識されないので動かない
not works
export default function Tabs({ children, buttons, buttonsContainer }) {
return (
<>
<buttonsContainer>{buttons}</buttonsContainer> // <--------
{children}
</>
);
}
works
大文字にすると動く
export default function Tabs({ children, buttons, buttonsContainer }) {
const ButtonContainer = buttonsContainer; // <----------
return (
<>
<ButtonContainer>{buttons}</ButtonContainer>
{children}
</>
);
}
そもそも大文字で受け取っても良い。
export default function Tabs({ children, buttons, ButtonsContainer = "menu" }) {
return (
<>
<ButtonsContainer>{buttons}</ButtonsContainer>
/public or /src/assets
You should use the public/ folder for any images that should not be handled by the build process and that should be generally available. Good candidates are images used directly in the index.html file or favicons.
On the other hand, images that are used inside of components should typically be stored in the src/ folder (e.g., in src/assets/).
react-scripts の挙動 〜なぜjs読み込んでないのに動くのか〜
ブラウザがindex.htmlを読む
react-scripts start はjs bundleを作った後、それをindex.htmlのbody最後またはheaderに強制的に埋め込む。
index.htmlを読んだブラウザはindex.js相当のbundleを読んでApp.jsなどを発動させる。
div id="root"にdomを埋め込む。
vite との違い
仕組みの違い
Create React App (react-scripts) の場合
public/index.html には <div id="root"></div> しか書かず、<script> タグは書きませんでした。ビルドツール(webpack + html-webpack-plugin)が、バンドルした JS を <script> タグとして 自動で挿入 してくれていたからです。
<!-- CRAではこれだけ。scriptは書かない -->
<body>
<div id="root"></div>
</body>
Vite (pnpm dev → vite) の場合
Vite は逆に、index.html を アプリの入口(エントリーポイント)そのもの として扱います。HTML を起点に、その中の <script type="module"> を辿ってモジュールグラフを構築する設計なので、エントリの script タグを 自分で書く必要があります。
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script> <!-- これが必須 -->
</body>
ポイント
| - | CRA (react-scripts) | Vite |
|---|---|---|
| index.html の場所 | public/ | プロジェクトルート |
| script タグ | 自動挿入(書かない) | 手動で書く |
| type="module" | 不要 | 必須(ネイティブ ES Modules を使うため) |
| エントリの考え方 | JS が入口 → HTML に注入 | HTML が入口 → script を辿る |
なので「Vite だから script タグが必要」というあなたの認識はその通りです。さらに言うと、type="module" が付いているのもポイントで、Vite は開発時にブラウザのネイティブ ES Modules 機能を直接使う(バンドルしない)ため、モジュールとして読み込ませる必要があります。
本番ビルド(vite build)の時は、Vite がこの <script> を解析して、最終的にハッシュ付きのバンドル済みファイルへのパスに自動で書き換えてくれます。
functionの場所
useStateのstate constとかに触らないなら component functionの外に書く。
参照配列/objectは直接書き換えてはならない
React は変更検知を参照比較で行います。
oldValue === newValue
例えば state がこうだったとします。
const [gameBoard, setGameBoard] = useState(INITIAL_GAME_BOARD);
もし state を直接変更すると、
gameBoard[0][0] = 'X';
setGameBoard(gameBoard);
gameBoard の中身は変わっていますが、参照先は同じです。
oldBoard === gameBoard // true
そのため React は
「同じオブジェクトが渡されたので変更なし」
と判断して再レンダリングしない可能性があります。
だから React では state を直接変更せず、
setGameBoard(prevBoard => {
const updatedBoard = prevBoard.map(row => [...row]);
updatedBoard[0][0] = 'X';
return updatedBoard;
});
のように新しい配列やオブジェクトを作って返します。
この場合は
oldBoard === updatedBoard // false
となるため、React は
「state が更新された」
と判断して再レンダリングできます。
要するに React で deep copy や immutable update が重要なのは、
- state の直接変更を防ぐため
- React の変更検知を正しく動作させるため
- 予期しない副作用を防ぐため
です。
これは別記事にした
Mutation と Immutable Update
https://qiita.com/uturned0/items/3c3e6deb0eeb8f68e331
辞書の key を変数にするには [] が必要
https://www.udemy.com/course/react-the-complete-guide-incl-redux/learn/lecture/39760372#overview
-
inputIdentifier: newValue- "inputIdentifier" という 文字列 キー
-
[inputIdentifier]: newValue- inputIdentifier 変数 の値をキー名として使う
Number / Unary Plus
例えば newValue === "42" のとき、全部 42 になります。
+newValue // 小数対応
Number(newValue) // 小数対応
parseInt(newValue, 10) // 整数化 ("42.9" → 42)
parseFloat(newValue) // 小数対応
+ はES1から使えるらしい。知らなかった!
React の <input type="number"> なら最近は
e.target.valueAsNumber
Number(e.target.value)
基礎からやろうとは思ったけど、まさか import から勉強したことなかったとはな。思い立って良かった。がんばろ。