Project & Tooling
Create React App ถูก deprecated แล้ว
หัวข้อที่มีชื่อว่า “Create React App ถูก deprecated แล้ว”หลายปีที่ผ่านมา คำตอบมาตรฐานของ “เริ่ม React app ยังไง?” คือ Create React App (CRA) ยุคนั้นจบแล้ว react.dev พูดตรง ๆ เลยว่า:
No. Create React App has been deprecated. For more information, see Sunsetting Create React App. — react.dev/learn/installation
และ blog post เองก็บอกชัดว่าต้องทำอะไรแทน:
Today, we’re deprecating Create React App for new apps, and encouraging existing apps to migrate to a framework, or to migrate to a build tool like Vite, Parcel, or RSBuild. — react.dev/blog
ตอนนี้ติดตั้ง CRA จะขึ้น warning ชี้ไปยังตัวเลือกปัจจุบัน อย่าเริ่มโปรเจกต์ใหม่ด้วย CRA และควรพิจารณาย้ายโปรเจกต์เดิมออก
ตัวแทนคือทางแยก: build tool สำหรับ client-side SPA หรือ framework สำหรับอะไรก็ตามที่ต้องการ routing, server rendering หรือ Server Components
Vite สำหรับ single-page app
หัวข้อที่มีชื่อว่า “Vite สำหรับ single-page app”สำหรับ SPA แบบ client-rendered Vite คือมาตรฐาน เช็ค currency ก่อนเสมอ — รันเองก่อน copy ตัวเลขจาก tutorial ไหนก็ตาม เพราะ command นี้แหละที่ทำให้บทนี้มีวันที่กำกับ:
npm view vite version # 8.3.1 at the time of writingnpm view @vitejs/plugin-react version # 6.1.1npm create vite@latest my-app -- --template react-tscd my-appnpm installnpm run devoutput จริงจาก command นั้นเป๊ะ ๆ ตอนนี้:
> npx> 'create-vite' my-app --template react-ts
◇ Scaffolding project in .../my-app...└ Done. Now run:
cd my-app npm install npm run devและ package.json ที่มันเขียนออกมา:
{ "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "oxlint", "preview": "vite preview" }, "dependencies": { "react": "^19.2.8", "react-dom": "^19.2.8" }, "devDependencies": { "@vitejs/plugin-react": "^6.1.1", "oxlint": "^1.81.0", "typescript": "~6.0.2", "vite": "^8.3.0" }}จุด currency สองจุดที่ทำให้คนที่ copy tutorial เก่ามาสะดุด:
-
template ไม่มี ESLint ให้แล้ว มันให้ oxlint แทน — linter ที่เขียนด้วย Rust, config ผ่าน
.oxlintrc.json, เปิดreact/rules-of-hooksมาให้เลย ถ้าอยากได้ compiler-aware rule ของeslint-plugin-react-hooks(ด้านล่าง) ต้องเพิ่ม ESLint เอง ไม่ได้มาให้ default อีกต่อไป -
@vitejs/plugin-reactv6 เปลี่ยน transform doc install ของ react.dev เองบอกชัด:@vitejs/plugin-reactuses Oxc. In@vitejs/[email protected], the inline Babel option was removed. — Vite’screate-viteReact template READMEก่อน v6 plugin ใช้ Babel สำหรับ Fast Refresh และ JSX ตั้งแต่ v6 เป็นต้นไป มันใช้ Oxc เป็น transform default — ติดตั้งเล็กลง ไม่ต้องพึ่ง Babel สำหรับกรณีทั่วไป
@vitejs/plugin-react-swcยังเป็นอีกตัวเลือกแยกต่างหาก: สลับไปใช้ SWC โดยเฉพาะเพื่อเร่ง cold start และ HMR บนโปรเจกต์ใหญ่มาก เลือก Oxc (default) ไว้ก่อน เว้นแต่มีเหตุผลเฉพาะที่ต้องใช้ SWC —react()เปล่า ๆ ไม่มี Babel plugin ตอนนี้คือทางที่เร็วแล้ว ไม่ใช่ทางช้าเหมือนก่อนguide
build-a-react-app-from-scratchของ react.dev เองก็พูดถึง RSbuild (ขับเคลื่อนด้วย Rspack) เป็นทางเลือกอีกอันคู่กับ Vite — ควรรู้จักชื่อไว้ แม้คอร์สนี้จะยึด Vite เป็นมาตรฐาน
React Compiler ของจริง
หัวข้อที่มีชื่อว่า “React Compiler ของจริง”บทเรียน the-react-compiler ในโมดูล React 19 โชว์ Babel transform output จริงไปแล้ว — อ่านอันนั้นก่อนถ้ายังไม่ได้อ่าน ส่วนนี้พิสูจน์ การเดินสาย install ให้ทำงานจริง ซึ่งบทนั้นไม่ได้ครอบคลุม
เพราะ @vitejs/[email protected]+ เอา option babel: {...} แบบ inline ออกไปแล้ว การเดินสาย compiler เลยต้องมี plugin ตัวที่สอง @rolldown/plugin-babel บวก helper reactCompilerPreset ที่ @vitejs/plugin-react v6 export มาให้เพื่อการนี้โดยเฉพาะ:
npm install -D babel-plugin-react-compiler @rolldown/plugin-babel// vite.config.ts — illustrative; not type-checked here (needs @rolldown/plugin-babel + babel-plugin-react-compiler, which the shared course probe doesn't install — the real, separate build proof is in the terminal output right below)import { defineConfig } from 'vite'import react, { reactCompilerPreset } from '@vitejs/plugin-react'import babel from '@rolldown/plugin-babel'
export default defineConfig({ plugins: [ react(), babel({ presets: [reactCompilerPreset()] }), ],})นี่ไม่ใช่ config ที่แค่ parse ผ่าน — มันถูกรันเต็มรูปแบบใน probe ของคอร์สนี้กับ Vite scaffold ค่า default npm run build (tsc -b && vite build):
vite v8.3.1 building client environment for production...✓ 22 modules transformed.dist/assets/index-C9S9Cn0y.js 223.97 kB │ gzip: 69.84 kB✓ built in 252msexit 0 และเมื่อตั้ง build.minify: false เพื่ออ่าน bundle ได้ ตัว output มี compiler runtime import อยู่จริง:
$ grep -c "compiler-runtime" dist/assets/index-*.js2สองจุดอ้างอิง — หนึ่งคือ import เอง อีกหนึ่งคือ entry ที่ reactCompilerPreset เติมให้ optimizeDeps.include อัตโนมัติ (react/compiler-runtime) นั่นคือ install ทั้งหมด: dev dependency สองตัว, plugin entry เพิ่มมาหนึ่งอัน แล้ว compiler ก็รันบน build จริงของคุณ — ไม่ต้องมี step แยก ไม่มี surprise ใน watch mode
eslint-plugin-react-hooks v7: flat config, error จริง
หัวข้อที่มีชื่อว่า “eslint-plugin-react-hooks v7: flat config, error จริง”เพราะ Vite template ไม่มี ESLint มาให้แล้ว เพิ่มเอง — แล้วเพิ่ม plugin ที่ให้ทั้ง Hooks rule คลาสสิกและ diagnostic ของ compiler เองมาเป็น lint error:
npm install -D eslint @typescript-eslint/parser eslint-plugin-react-hooksimport reactHooks from 'eslint-plugin-react-hooks';import tsParser from '@typescript-eslint/parser';import { defineConfig } from 'eslint/config';
export default defineConfig([ { files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser } }, reactHooks.configs.flat['recommended-latest'],]);recommended-latest ([email protected] เวอร์ชันที่ probe ของคอร์สนี้ติดตั้งไว้) เปิดทั้ง rule-of-hooks และ rule เฉพาะของ compiler ในชุด preset เดียว — ไม่ต้องเพิ่ม package แยกสำหรับ compiler เลย นี่คือการ violate จริงสองแบบ รันผ่าน config ด้านบนเป๊ะ ๆ:
import { useState } from 'react';
export function Toggle({ show }: { show: boolean }) { if (show) { const [on, setOn] = useState(false); // hook called conditionally return <button onClick={() => setOn(!on)}>{String(on)}</button>; } return null;}Bad.tsx 5:25 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooksimport { useEffect, useState } from 'react';
export function Counter({ step }: { step: number }) { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => setCount((c) => c + step), 1000); return () => clearInterval(id); }, []); // missing dependency: step return <output>{count}</output>;}Counter.tsx 8:6 warning React Hook useEffect has a missing dependency: 'step'. Either include it or remove the dependency array. If 'setCount' needs the current value of 'step', you can also switch to useReducer instead of useState and read 'step' in the reducer react-hooks/exhaustive-depsทั้งสองอันจริง ทั้งสองอันมาจาก config ข้างบนตรง ๆ — ไม่ได้เขียน error text เอง
TypeScript strict ตั้งแต่ต้น
หัวข้อที่มีชื่อว่า “TypeScript strict ตั้งแต่ต้น”probe ของคอร์สนี้เอง (และทุก scaffold ข้างบน) เปิด strict บวกอีกสอง setting ที่ควรรู้จักชื่อไว้ เพราะมันเปลี่ยนความหมายของ “TypeScript ที่ valid” ใน setup นี้โดยเฉพาะ:
erasableSyntaxOnly— ปฏิเสธ construct ที่มีแค่ใน TS runtime (enum, constructor parameter property) ที่ erase ไปเป็นค่าว่างในระดับ type ไม่ได้ Node สมัยใหม่รัน.tsตรง ๆ ได้ด้วยการ strip type ออก flag นี้ทำให้ code ของคุณเข้ากันได้กับแบบนั้นโดยโครงสร้างnoUnusedLocals/noUnusedParameters— import หรือ prop ที่ destructure มาแล้วไม่ใช้คือ compile error ไม่ใช่แค่ lint warning code ที่ generate มาแล้วมี import เหลือค้างจะ build พังตรงนี้ ไม่ใช่แค่eslintบ่น
บทเรียน TypeScript with React ลงลึกเรื่อง type component, hooks และ event ภายใต้ strict config ชุดเดียวกันนี้
framework สำหรับ routing, SSR และ RSC
หัวข้อที่มีชื่อว่า “framework สำหรับ routing, SSR และ RSC”เมื่อไรที่ต้องการ routing จริง, server-side rendering, data loading หรือ React Server Components ให้หยิบ framework มาใช้แทนการประกอบเอง คำแนะนำปัจจุบันจาก React docs:
npx create-next-app@latest # Next.js — App Router, RSC, SSRnpx create-react-router@latest # React Router (framework mode) — routing + SSRflowchart TB
q{"ต้องการ routing / SSR /
Server Components?"}
q -->|no, client SPA| vite["Vite
(build tool, Oxc transform)"]
q -->|yes| fw["Framework:
Next.js or React Router"]
cra["Create React App"] -->|deprecated 2025| x["do not use"] framework ดูแลเรื่องที่ build tool ทิ้งให้คุณจัดการ: nested route, data fetching บน server, streaming และ RSC boundary (อยู่ในโมดูล React 19) สำหรับ SPA แบบ dashboard หลัง login Vite เพียงพอ; สำหรับ content site หรืออะไรที่เน้น SEO หรือ server-data ก็คุ้มที่จะใช้ framework
dev experience
หัวข้อที่มีชื่อว่า “dev experience”ไม่ว่าเลือกอันไหน สิ่งเหล่านี้ทำให้การพัฒนา React สนุก:
- React DevTools (browser extension) — ดู component tree, props, state และ hooks; Profiler บอกว่าทำไมและ component render บ่อยแค่ไหน (สำคัญมากในโมดูล performance)
- Fast Refresh — แก้ component แล้วเห็นผลทันที โดย เก็บ state ของ component ไว้ built-in ใน Vite และ framework
- JSX transform สมัยใหม่ — ไม่ต้อง
import React from 'react'ที่หัวทุกไฟล์แค่เพื่อใช้ JSX อีกต่อไป; compiler inject runtime import ให้ (ยังต้อง import hooks และ API ที่ใช้) - Strict Mode — ครอบ app ด้วย
<StrictMode>ใน development เพื่อจับ render ที่ไม่ pure และบั๊ก effect (การ double-invoke ที่คุณเจอใน Foundations)
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”tooling พวกนี้ไม่ใช่ React เลย React ship แค่ rendering library เท่านั้นโดยตั้งใจ — ทุกตัวเลือกในบทนี้ (Oxc เทียบ SWC transform, ESLint เทียบ oxlint, Vite เทียบ framework) คือคำตอบที่ community เห็นตรงกันว่า “จะใส่อะไรรอบ ๆ React” ไม่ใช่ API ของ React เอง อ่าน currency claim ในบทนี้แบบเดียวกัน: เป็น “สิ่งที่ลงตัวกัน ณ เลขเวอร์ชันที่โชว์ไว้” เช็คซ้ำได้ด้วย command npm view / scaffold เป๊ะ ๆ ที่ให้ไว้ ไม่ใช่สิ่งที่ต้องจำ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | ได้อะไร | ต้นทุน |
|---|---|---|
Vite + @vitejs/plugin-react (Oxc, default) | ติดตั้งเล็กลง ไม่ต้องพึ่ง Babel ทางที่เร็วที่สุดสำหรับกรณีทั่วไป | ไม่มี Babel plugin แบบ inline — compiler ต้องเดินสายแยกผ่าน @rolldown/plugin-babel ตามที่โชว์ข้างบน |
@vitejs/plugin-react-swc | cold start/HMR เร็วกว่าบน codebase ใหญ่มาก | ต้องรู้จัก transform ตัวที่สองเพิ่ม ไม่จำเป็นถ้า Oxc ยังไม่ใช่คอขวดจริง |
| framework (Next.js / React Router) แทน Vite | routing, SSR, RSC จัดการให้ | ต้องเรียนรู้เพิ่ม โครงสร้างมี opinion มากกว่า บ่อยครั้งเกินความจำเป็นสำหรับ SPA เล็ก ๆ |
oxlint (default ของ scaffold) เทียบเพิ่ม ESLint + eslint-plugin-react-hooks | oxlint เร็วทันที ไม่ต้อง config มี react/rules-of-hooks มาให้ | เฉพาะ ESLint recommended-latest เท่านั้นที่ให้ diagnostic ของ React Compiler เองเป็น lint error |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “CRA เก่าแต่ยังใช้ได้กับโปรเจกต์เล็ก ๆ” guide install ของ react.dev เองตอบตรง ๆ: “No. Create React App has been deprecated.” ไม่ใช่แค่เรื่องรสนิยมอีกต่อไป — doc พาคุณออกห่างจากมันจริง ๆ
- “เพิ่ม
@vitejs/plugin-reactแล้วได้ Babel มาด้วย” ไม่ใช่ตั้งแต่ v6 — ทางเดิน default คือ Oxc ไม่มี Babel dependency เลย คุณเพิ่ม Babel กลับมาเอง (ผ่าน@rolldown/plugin-babel) ก็ต่อเมื่อต้องการ plugin ที่เป็น Babel-only อย่าง React Compiler - “Vite React template มี ESLint มาให้” มันมี oxlint ให้ ถ้าโปรเจกต์ที่ generate มามี
eslint.config.jsที่คุณไม่ได้เพิ่มเอง เช็คว่ามันรันจริงไหม — config ที่ว่างเปล่าหรือค้างเก่าแย่กว่าไม่มี config เพราะดูเหมือนกำลัง lint อยู่ทั้งที่ไม่ได้ทำอะไร
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”- โปรเจกต์ยังมีไฟล์
.eslintrc.*แบบเก่าอยู่คู่กับ ESLint เวอร์ชันปัจจุบันไหม? ESLint 9+ เป็น flat-config-only โดย default และปฏิเสธที่จะรันเลยถ้าเจอ.eslintrc.*โดยไม่มีeslint.config.*— นี่ไม่ใช่เรื่อง style แต่คือ fail แบบเต็ม ๆ - ถ้า
vite.config.tsส่งreact({ babel: { plugins: [...] } })เช็คเวอร์ชัน@vitejs/plugin-reactที่ติดตั้งอยู่ — option นั้นไม่มีตั้งแต่ v6.0.0 เป็นต้นไป และขึ้นอยู่กับ type ที่ใช้ การส่ง option นี้อาจเป็น silent no-op แทนที่จะ error ตอน build - hook ถูกเรียกหลัง
returnแบบ early หรือในนั้นifไหม?react-hooks/rules-of-hooksจะจับได้ก็ต่อเมื่อ config ถูกตั้งและรันจริง — เช็คว่า lint command ถูกเดินสายเข้า CI แล้ว ไม่ใช่แค่มีอยู่ในpackage.json eslint-plugin-react-hooksตั้งเป็นrecommended(แค่ rules-of-hooks + exhaustive-deps) ทั้งที่โปรเจกต์อยากได้ diagnostic ของ React Compiler ด้วย (immutability,purity,set-state-in-render, …) ไหม? อันหลังมาจากrecommended-latestเท่านั้น
🐛 หา bug ในโค้ดนี้
import react from '@vitejs/plugin-react'
export default {plugins: [ react({ babel: { plugins: ['babel-plugin-react-compiler'] } }),],}เฉลย
นี่คือวิธีเดินสาย React Compiler เข้า @vitejs/plugin-react แบบก่อน v6.0.0 ที่ถูก copy มาใช้ในโปรเจกต์ที่รัน plugin เวอร์ชันปัจจุบัน เช็คตรง ๆ กับ type definition ของ @vitejs/[email protected] เอง: Options ไม่มี field babel อีกต่อไป — การส่ง option นี้เข้าไปจะ type-check ไม่ผ่านภายใต้ strict TypeScript หรือไม่ก็ถูกเมินเฉยตอน runtime แล้วแต่ config เขียนแบบไหน ไม่ว่าทางไหน compiler ก็ไม่เคยรัน โดยไม่มี error บอกเลย การเดินสายปัจจุบันต้องมี plugin ตัวที่สองแยกต่างหาก: @rolldown/plugin-babel โดยส่ง compiler เข้าไปเป็น preset ผ่าน reactCompilerPreset() — พิสูจน์ด้วย vite build จริงก่อนหน้านี้ในบทเรียน (grep -c "compiler-runtime" dist/assets/index-*.js → 2 ยืนยันว่า transform รันจริง) เช็ค npm ls @vitejs/plugin-react ก่อน copy install snippet เสมอ — นี่คือรายละเอียดที่ล้าสมัยเร็วมากระหว่าง major version ของ plugin