React Interview Prep — คำถามที่พบบ่อย
1. Virtual DOM คืออะไร และทำไมถึง "เร็วกว่า" manipulate DOM ตรงๆ?
Virtual DOM คือ JavaScript object tree ที่จำลองโครงสร้าง UI ไว้ในหน่วยความจำ — React diff virtual DOM ใหม่กับเก่า หาเฉพาะส่วนต่าง แล้วอัปเดต real DOM เท่าที่จำเป็น
จำว่า "Virtual DOM ไม่ได้เร็วกว่า DOM โดยตัวมันเอง มันแค่ช่วยลดจำนวนครั้งที่แตะ real DOM" — Beyond: คำตอบที่ "ผิวเผิน" คือ "virtual DOM เร็วกว่า DOM จริง" ซึ่งไม่แม่นยำนัก เพราะการ สร้าง/diff virtual DOM เองก็มี cost (เป็น JavaScript computation) ถ้าเขียนโค้ด manipulate DOM ตรงๆ อย่างฉลาด (เปลี่ยนเฉพาะจุดที่จำเป็นจริงๆ เหมือนที่ React ทำ) ก็เร็วกว่าหรือเท่ากับ React ได้เสมอ — สิ่งที่ React ให้จริงๆ ไม่ใช่ "ความเร็วดิบ" แต่คือ developer ergonomics: เขียนโค้ดแบบ declarative ("UI ควรหน้าตาแบบนี้ตาม state") โดยไม่ต้องคิดเองว่า "จะอัปเดต DOM ทีละจุดยังไงให้ efficient" React จัดการ diffing ให้อัตโนมัติ แลกกับ overhead เล็กน้อยที่ ส่วนใหญ่ไม่มีนัยสำคัญเทียบกับเวลาพัฒนาที่ประหยัดได้
2. useEffect กับ useLayoutEffect ต่างกันยังไง?
useEffect รันแบบ asynchronous หลัง browser วาดหน้าจอ (paint) เสร็จแล้ว ส่วน
useLayoutEffect รันแบบ synchronous ก่อน browser paint (บล็อก paint จนกว่าจะรันเสร็จ)
จำว่า "useEffect = ทำทีหลังไม่รีบ, useLayoutEffect = ต้องทำให้เสร็จก่อนใครเห็น" —
Beyond: ใช้ useLayoutEffect เมื่อ effect นั้นวัดหรือแก้ DOM แล้วผลกระทบต่อสิ่งที่ user
เห็นทันที (เช่น วัดขนาด element แล้วปรับ position ก่อน paint) ถ้าใช้ useEffect กับ use
case นี้ user จะเห็น UI "กระพริบ" (flicker) แวบหนึ่งก่อนตำแหน่งจะถูกปรับ เพราะ browser paint
ไปแล้วรอบหนึ่งก่อนที่ effect จะทันแก้ไข — แต่ useLayoutEffect ควรใช้ให้น้อยที่สุดเท่าที่จำเป็น
เพราะมันบล็อก rendering จริงๆ ถ้าโค้ดข้างในช้า จะทำให้ UI ค้างได้
3. Controlled vs Uncontrolled Component ต่างกันยังไง?
Controlled: ค่าของ input ถูกควบคุมผ่าน React state เต็มรูปแบบ (value + onChange)
Uncontrolled: ให้ DOM จัดการค่าของตัวเอง อ่านค่าผ่าน ref ตอนต้องการเท่านั้น
จำว่า "Controlled = React ถือสมุดบันทึกให้ตลอดเวลา, Uncontrolled = DOM จำเอง React แค่ไป
เปิดดูทีหลัง" — Beyond: Controlled component มี cost ที่คนมักมองข้าม: ทุกการกดแป้นพิมพ์
trigger re-render ของ component ทั้งก้อน (เพราะ state เปลี่ยนทุกตัวอักษร) ในฟอร์มใหญ่ที่มี
field เยอะมากๆ (หลักร้อย field) นี่อาจกลายเป็นปัญหา performance จริง วิธีแก้ที่ library อย่าง
React Hook Form ใช้คือพึ่ง uncontrolled component เป็นหลัก (ใช้ ref อ่านค่าแทนการ
ผูกกับ state) แล้วให้ re-render เกิดเฉพาะตอน validate/submit เท่านั้น ไม่ใช่ทุกตัวอักษรที่พิมพ์
— เป็นเหตุผลเชิง performance จริงที่อยู่เบื้องหลังว่าทำไม React Hook Form ถึงเร็วกว่า
Formik (ที่ใช้ controlled component เป็นหลัก) อย่างมีนัยสำคัญในฟอร์มขนาดใหญ่
4. ทำไมไม่ควร mutate state ตรงๆ (เช่น state.push(x))?
React ตรวจสอบว่า state เปลี่ยนไหมด้วย reference comparison (Object.is) ไม่ใช่ deep
comparison — ถ้า mutate object/array เดิมตรงๆ reference จะไม่เปลี่ยน React จะคิดว่า "ไม่มี
อะไรเปลี่ยน" แล้วไม่ re-render ให้
จำว่า "React เช็คแค่ 'ที่อยู่เปลี่ยนไหม' ไม่ได้เปิดดูของข้างในละเอียด" — Beyond: เหตุผล
ที่ React เลือก reference comparison แทน deep comparison ไม่ใช่แค่เรื่อง performance (deep
compare ของ object ใหญ่ๆ แพง) แต่เป็นเรื่อง ความแน่นอน (predictability): deep comparison
ต้องนิยามว่า "เท่ากัน" แปลว่าอะไรสำหรับทุก edge case (NaN, circular reference, function ใน
object, Date object ฯลฯ) ซึ่งซับซ้อนและมีข้อยกเว้นเยอะ reference comparison ชัดเจนตายตัว 100%
ไม่มีข้อยกเว้น (=== เท่ากันหรือไม่เท่ากันเท่านั้น) — นี่คือเหตุผลที่ library อย่าง Immer
(ใช้คู่กับ Redux Toolkit) ได้รับความนิยม: เขียนโค้ดราวกับ mutate ตรงๆ ได้ (state.push(x))
แต่ Immer สร้าง object ใหม่ (reference ใหม่) ให้เบื้องหลังผ่าน Proxy โดยอัตโนมัติ ได้ทั้ง
developer ergonomics ของการเขียนแบบ mutate และความถูกต้องของ immutability พร้อมกัน
5. Server Component กับ Client Component (React 18+/Next.js App Router) ต่างกันยังไง?
Server Component render บน server เท่านั้น (ไม่ส่ง JavaScript ของ component นั้นไปที่ browser
เลย) เข้าถึง backend resource (database, filesystem) ได้ตรงๆ แต่ใช้ hook/event handler
(interactive) ไม่ได้ Client Component (ต้องมี "use client" directive) ทำงานใน browser
ตามปกติ ใช้ state/effect/event ได้เต็มที่
จำว่า "Server Component = เชฟทำอาหารในครัว ส่งจานที่เสร็จแล้วมาเสิร์ฟ, Client Component =
โต๊ะที่ลูกค้านั่งปรุงรสเอง (interactive)" — Beyond: ประโยชน์หลักของ Server Component ไม่ใช่
แค่ "render เร็วกว่า" แต่คือ ลด JavaScript bundle size ที่ส่งไป browser อย่างมีนัยสำคัญ
(code ของ Server Component ไม่ถูกส่งไปเลย ต่างจาก SSR แบบเดิมที่ยัง hydrate component ทั้งหมด
ที่ client ต้องโหลด JS มาด้วย) และเข้าถึง secret/database credential ได้อย่างปลอดภัย
(โค้ดไม่เคยไปถึง browser เลย) ข้อควรระวังที่คนมักพลาดคือ Server Component import Client
Component ได้ (ส่งต่อ "ช่อง" ให้ไปทำงานที่ client) แต่ Client Component import Server
Component ตรงๆ ไม่ได้ (ต้องรับมาเป็น children prop แทน) เพราะทิศทางการไหลของ "boundary"
นี้เป็นทางเดียว — เป็นกฎที่คนใหม่ที่เริ่มใช้ App Router มักงงว่าทำไม import ตรงๆ แล้ว error
Virtual DOM's real value isn't raw speed (hand-optimized direct DOM manipulation can match or
beat it) — it's declarative developer ergonomics, with diffing overhead usually negligible
against development time saved. useLayoutEffect runs synchronously before paint (for DOM
measurements that must apply before the user sees anything) versus useEffect's async after-
paint timing, which can cause visible flicker if misused for layout-affecting work. Controlled
components re-render on every keystroke, which is why performance-focused form libraries like
React Hook Form lean on uncontrolled inputs (refs) instead. React detects state changes via
reference equality, not deep comparison, for predictability and performance — Immer's Proxy-based
approach lets you write mutation-style code while still producing new references under the hood.
Server Components (React 18+/Next.js App Router) never ship their JS to the browser and can
safely touch backend resources directly, while Client Components need "use client" and handle
interactivity — the import direction is one-way: Server Components can render Client Components,
but not vice versa (Client Components receive Server Components only via children).