30 คำถามสัมภาษณ์ React ที่พบบ่อยที่สุด: คู่มือเตรียมตัวฉบับสมบูรณ์
รวม 30 คำถามสัมภาษณ์ React ที่พบบ่อยที่สุดในปี 2026 พร้อมคำตอบเชิงลึกและตัวอย่างโค้ดเพื่อช่วยให้ผู้สมัครคว้าตำแหน่งนักพัฒนา React

การสัมภาษณ์เทคนิค React จะวัดความเข้าใจในแนวคิดพื้นฐาน รูปแบบขั้นสูง และแนวปฏิบัติที่ดี คู่มือนี้รวบรวม 30 คำถามที่พบบ่อยที่สุด พร้อมคำตอบเชิงลึกและตัวอย่างโค้ดเพื่อให้การเตรียมตัวเป็นไปอย่างมีประสิทธิภาพ
คำถามเหล่านี้ถูกจัดเรียงตามระดับความยาก การฝึกฝนพื้นฐานให้แม่นยำก่อนเข้าสู่แนวคิดขั้นสูงจะช่วยให้การเตรียมตัวมีโครงสร้างที่ชัดเจนยิ่งขึ้น
พื้นฐานของ React
1. Virtual DOM คืออะไรและทำไม React ถึงใช้
Virtual DOM คือการแทนค่าของ DOM จริงในรูปแบบ JavaScript ที่มีน้ำหนักเบา React ใช้สิ่งนี้เพื่อปรับแต่งการอัปเดตอินเทอร์เฟซให้มีประสิทธิภาพ
กระบวนการประกอบด้วยสามขั้นตอน ได้แก่ React สร้างสำเนาเสมือนของ DOM ก่อน จากนั้นเปรียบเทียบสำเนาดังกล่าวกับเวอร์ชันก่อนหน้าเมื่อมีการเปลี่ยนแปลง (อัลกอริทึม diffing) และสุดท้ายนำเฉพาะการเปลี่ยนแปลงที่จำเป็นไปใช้กับ DOM จริง (reconciliation)
// Simplified example of the concept
// When state changes, React doesn't recreate the entire DOM
function Counter() {
const [count, setCount] = useState(0)
// Only the span containing count will be updated in the real DOM
// The rest of the component is untouched
return (
<div>
<h1>Counter</h1>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}วิธีการนี้หลีกเลี่ยงการดำเนินการที่มีต้นทุนสูงบน DOM และทำให้สามารถอัปเดตอินเทอร์เฟซที่ซับซ้อนได้อย่างรวดเร็ว
2. คอมโพเนนต์แบบฟังก์ชันกับแบบคลาสต่างกันอย่างไร
คอมโพเนนต์แบบฟังก์ชันคือฟังก์ชัน JavaScript ที่รับ props และคืนค่าเป็น JSX นับตั้งแต่ React 16.8 เป็นต้นมา ฮุกอนุญาตให้ใช้ state และ lifecycle ในคอมโพเนนต์แบบฟังก์ชันได้
// Functional component (recommended)
// More concise, easier to test, supports hooks
function Welcome({ name }) {
const [visits, setVisits] = useState(0)
useEffect(() => {
setVisits(v => v + 1)
}, [])
return <h1>Hello {name}, visit #{visits}</h1>
}
// Class component (legacy)
// More verbose, requires this binding
class WelcomeClass extends React.Component {
state = { visits: 0 }
componentDidMount() {
this.setState(prev => ({ visits: prev.visits + 1 }))
}
render() {
return <h1>Hello {this.props.name}, visit #{this.state.visits}</h1>
}
}ปัจจุบันคอมโพเนนต์แบบฟังก์ชันถือเป็นมาตรฐาน คอมโพเนนต์แบบคลาสยังคงรองรับอยู่แต่ไม่แนะนำสำหรับโปรเจกต์ใหม่
3. JSX ทำงานอย่างไร
JSX คือส่วนขยายไวยากรณ์ของ JavaScript ที่อนุญาตให้เขียนมาร์กอัปภายในโค้ด JSX ไม่ใช่ HTML แต่เป็น JavaScript ในรูปแบบที่อ่านง่ายกว่า
// What we write (JSX)
const element = (
<div className="container">
<h1>Title</h1>
<p>Paragraph</p>
</div>
)
// What Babel compiles (pure JavaScript)
const element = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Paragraph')
)ความแตกต่างจาก HTML ได้แก่ การใช้ className แทน class, htmlFor แทน for, การใช้ camelCase สำหรับแอตทริบิวต์ (onClick, tabIndex) และต้องปิดแท็กที่ปิดในตัวเองเสมอ
4. state กับ props ต่างกันอย่างไร
Props คือข้อมูลที่ส่งจากคอมโพเนนต์แม่ไปยังลูก เป็นข้อมูลที่อ่านได้อย่างเดียว ส่วน state คือสถานะภายในของคอมโพเนนต์ที่สามารถเปลี่ยนแปลงผ่าน setter ได้
// name and role are props (immutable)
function UserCard({ name, role }) {
// isExpanded is state (mutable)
const [isExpanded, setIsExpanded] = useState(false)
return (
<div className="card">
<h2>{name}</h2>
<p>{role}</p>
{/* Modifying state triggers a re-render */}
<button onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? 'Collapse' : 'Details'}
</button>
{isExpanded && <UserDetails name={name} />}
</div>
)
}
// Usage
<UserCard name="Alice" role="Developer" />หลักการพื้นฐานคือ props ไหลจากบนลงล่าง (พ่อแม่ไปยังลูก) ส่วน state เป็นของแต่ละคอมโพเนนต์โดยเฉพาะ
5. ทำไม key ในรายการจึงสำคัญ
Key ช่วยให้ React ระบุได้ว่ามีองค์ประกอบใดเปลี่ยนแปลง เพิ่ม หรือถูกลบออกจากรายการ หากไม่มี key ที่ไม่ซ้ำและคงที่ React อาจแสดงพฤติกรรมที่ไม่คาดคิด
// ❌ Bad practice: index as key
// Problem: if order changes, React loses tracking
{items.map((item, index) => (
<ListItem key={index} item={item} />
))}
// ✅ Good practice: unique and stable identifier
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
// Concrete example of the problem with indices
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Create a project' }
])
// When deleting the first element with key={index}
// React will think element 0's content changed
// instead of understanding an element was removed
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}React Hooks
6. อธิบาย useState และข้อผิดพลาดที่พบบ่อย
useState ใช้จัดการ state ภายในคอมโพเนนต์แบบฟังก์ชัน setter รับได้ทั้งค่าใหม่หรือฟังก์ชันสำหรับอัปเดต
// Declaration with initial value
const [count, setCount] = useState(0)
// ❌ Pitfall: multiple updates in the same cycle
function increment() {
setCount(count + 1) // count = 0, sets 1
setCount(count + 1) // count = 0 still, sets 1
setCount(count + 1) // count = 0 still, sets 1
// Final result: 1 (not 3)
}
// ✅ Solution: use the update function
function incrementCorrect() {
setCount(prev => prev + 1) // 0 → 1
setCount(prev => prev + 1) // 1 → 2
setCount(prev => prev + 1) // 2 → 3
// Final result: 3
}
// ❌ Pitfall: object mutation
const [user, setUser] = useState({ name: 'Alice', age: 25 })
user.age = 26 // Direct mutation, no re-render
// ✅ Solution: create a new object
setUser({ ...user, age: 26 })
// or
setUser(prev => ({ ...prev, age: 26 }))7. useEffect ทำงานอย่างไรกับอาร์เรย์ของ dependency
useEffect ทำงาน side effect หลังจากการเรนเดอร์ อาร์เรย์ของ dependency ควบคุมว่าเอฟเฟกต์จะถูกเรียกเมื่อใด
// Executed on every render (rare, usually avoid)
useEffect(() => {
console.log('Render completed')
})
// Executed only on mount (equivalent to componentDidMount)
useEffect(() => {
console.log('Component mounted')
// Cleanup on unmount (equivalent to componentWillUnmount)
return () => {
console.log('Component unmounted')
}
}, [])
// Executed when userId changes
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`)
const data = await response.json()
setUser(data)
}
fetchUser()
}, [userId])
// ❌ Missing dependency - subtle bug
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1) // count is "captured" at its initial value
}, 1000)
return () => clearInterval(timer)
}, []) // count is missing from dependencies
// ✅ Fix with update function
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1) // No need for count in deps
}, 1000)
return () => clearInterval(timer)
}, [])ควรเปิดใช้งาน eslint-plugin-react-hooks เสมอเพื่อตรวจจับ dependency ที่ขาดหายไป กฎนี้ช่วยป้องกันบั๊กที่วินิจฉัยได้ยากจำนวนมาก
8. เมื่อใดควรใช้ useMemo และ useCallback
ฮุกทั้งสองนี้ใช้สำหรับ memoization เพื่อหลีกเลี่ยงการคำนวณซ้ำหรือการสร้างค่าใหม่โดยไม่จำเป็น ควรระวังไม่ใช้มากเกินไป
// useMemo: memoizes a computed value
function ProductList({ products, filter }) {
// Recalculated only if products or filter change
const filteredProducts = useMemo(() => {
console.log('Filtering...')
return products.filter(p => p.category === filter)
}, [products, filter])
return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
// useCallback: memoizes a function
function ParentComponent() {
const [count, setCount] = useState(0)
// Without useCallback, handleClick is recreated on every render
// Causing unnecessary re-renders of ExpensiveChild
const handleClick = useCallback((id) => {
console.log('Clicked:', id)
}, []) // Empty deps = stable function
return (
<>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
{/* React.memo on ExpensiveChild for this to be effective */}
<ExpensiveChild onClick={handleClick} />
</>
)
}
// ❌ Over-optimization: not needed here
const SimpleComponent = () => {
// This calculation is trivial, useMemo adds overhead
const doubled = useMemo(() => 2 * 2, [])
return <span>{doubled}</span>
}ควรใช้ฮุกเหล่านี้เฉพาะเมื่อพบปัญหาด้านประสิทธิภาพที่ชัดเจน หรือเมื่อต้องการรักษา reference ที่ส่งให้กับคอมโพเนนต์ที่ถูก memoize ไว้ให้คงที่
9. useRef ทำงานอย่างไรและใช้ในกรณีใดบ้าง
useRef สร้าง reference ที่เปลี่ยนแปลงได้และคงอยู่ระหว่างการเรนเดอร์ โดยไม่ทำให้เกิดการเรนเดอร์ใหม่เมื่อมีการเปลี่ยนแปลงค่า
// Case 1: Access a DOM element
function TextInput() {
const inputRef = useRef(null)
const focusInput = () => {
inputRef.current.focus()
}
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</>
)
}
// Case 2: Store a mutable value without re-render
function Timer() {
const [seconds, setSeconds] = useState(0)
const intervalRef = useRef(null)
const start = () => {
// Store the interval ID to be able to stop it
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
}
const stop = () => {
clearInterval(intervalRef.current)
}
return (
<div>
<span>{seconds}s</span>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
)
}
// Case 3: Keep the previous value
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}10. อธิบาย useContext และเมื่อใดควรใช้
useContext ช่วยให้เข้าถึง context ของ React โดยไม่ต้องส่ง props ทอดต่อกันหลายชั้น เหมาะสำหรับข้อมูลแบบ global เช่น ธีมหรือผู้ใช้ที่ล็อกอินอยู่
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
})
// 2. Create the provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}, [])
// Memoize the value to avoid unnecessary re-renders
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme])
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
)
}
// 3. Use the context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext)
return (
<button
onClick={toggleTheme}
className={theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-800'}
>
{theme === 'dark' ? 'Light' : 'Dark'} mode
</button>
)
}
// 4. Wrap the application
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<Footer />
</ThemeProvider>
)
}พร้อมที่จะพิชิตการสัมภาษณ์ React / Next.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
รูปแบบขั้นสูง
11. Higher-Order Component (HOC) คืออะไร
HOC คือฟังก์ชันที่รับคอมโพเนนต์เข้ามาและคืนค่าเป็นคอมโพเนนต์ใหม่ที่ถูกเสริมความสามารถ ใช้น้อยลงตั้งแต่มีฮุก แต่ยังคงพบได้ในไลบรารีบางตัว
// HOC that adds logging
function withLogging(WrappedComponent) {
return function WithLogging(props) {
useEffect(() => {
console.log(`${WrappedComponent.name} mounted with props:`, props)
return () => {
console.log(`${WrappedComponent.name} unmounted`)
}
}, [])
return <WrappedComponent {...props} />
}
}
// HOC that handles authentication
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const { user, isLoading } = useAuth()
if (isLoading) return <LoadingSpinner />
if (!user) return <Navigate to="/login" />
return <WrappedComponent {...props} user={user} />
}
}
// Usage
const ProtectedDashboard = withAuth(Dashboard)
const LoggedButton = withLogging(Button)12. อธิบายรูปแบบ Render Props
รูปแบบ Render Props ช่วยแบ่งปันลอจิกระหว่างคอมโพเนนต์โดยส่งผ่าน prop ที่เป็นฟังก์ชัน
// Component with render prop
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [])
// Call the render function with data
return render(position)
}
// Usage
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<div>
Position: {x}, {y}
</div>
)}
/>
)
}
// Modern version with custom hook (preferred)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [])
return position
}
function App() {
const { x, y } = useMousePosition()
return <div>Position: {x}, {y}</div>
}13. วิธีสร้าง custom hook
Custom hook ใช้สำหรับแยกและนำลอจิกที่มี state ไปใช้ร่วมกันระหว่างคอมโพเนนต์ต่างๆ
function useLocalStorage(key, initialValue) {
// Initialize with localStorage value or default
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(error)
return initialValue
}
})
// Setter wrapper that syncs with localStorage
const setValue = useCallback((value) => {
try {
// Support update functions
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.error(error)
}
}, [key, storedValue])
return [storedValue, setValue]
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light')
const [fontSize, setFontSize] = useLocalStorage('fontSize', 16)
return (
<div>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<input
type="range"
min="12"
max="24"
value={fontSize}
onChange={e => setFontSize(Number(e.target.value))}
/>
</div>
)
}14. รูปแบบ Compound Components คืออะไร
รูปแบบนี้ช่วยสร้างคอมโพเนนต์ที่ทำงานร่วมกันโดยปริยาย คล้ายกับการใช้แท็ก <select> และ <option>
// Shared context between components
const TabsContext = createContext()
function Tabs({ children, defaultValue }) {
const [activeTab, setActiveTab] = useState(defaultValue)
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
)
}
function TabList({ children }) {
return <div className="tab-list" role="tablist">{children}</div>
}
function Tab({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext)
const isActive = activeTab === value
return (
<button
role="tab"
aria-selected={isActive}
className={`tab ${isActive ? 'active' : ''}`}
onClick={() => setActiveTab(value)}
>
{children}
</button>
)
}
function TabPanel({ value, children }) {
const { activeTab } = useContext(TabsContext)
if (activeTab !== value) return null
return (
<div role="tabpanel" className="tab-panel">
{children}
</div>
)
}
// Attach sub-components
Tabs.List = TabList
Tabs.Tab = Tab
Tabs.Panel = TabPanel
// Intuitive usage
function App() {
return (
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Tab value="profile">Profile</Tabs.Tab>
<Tabs.Tab value="settings">Settings</Tabs.Tab>
<Tabs.Tab value="billing">Billing</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="profile">Profile content...</Tabs.Panel>
<Tabs.Panel value="settings">Settings content...</Tabs.Panel>
<Tabs.Panel value="billing">Billing content...</Tabs.Panel>
</Tabs>
)
}15. วิธีนำรูปแบบ Controlled vs Uncontrolled ไปใช้
คอมโพเนนต์แบบ controlled มี state ที่ถูกจัดการโดย React ส่วนแบบ uncontrolled ใช้ DOM โดยตรง
// CONTROLLED component
// State is in React, component reflects that state
function ControlledInput() {
const [value, setValue] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
console.log('Submitted value:', value)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
)
}
// UNCONTROLLED component
// State is in the DOM, we read it when needed
function UncontrolledInput() {
const inputRef = useRef(null)
const handleSubmit = (e) => {
e.preventDefault()
console.log('Submitted value:', inputRef.current.value)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
defaultValue=""
/>
<button type="submit">Submit</button>
</form>
)
}
// Component that supports BOTH modes
function FlexibleInput({ value, defaultValue, onChange }) {
const isControlled = value !== undefined
const [internalValue, setInternalValue] = useState(defaultValue || '')
const currentValue = isControlled ? value : internalValue
const handleChange = (e) => {
if (!isControlled) {
setInternalValue(e.target.value)
}
onChange?.(e.target.value)
}
return (
<input
type="text"
value={currentValue}
onChange={handleChange}
/>
)
}ประสิทธิภาพและการเพิ่มประสิทธิภาพ
16. React.memo ทำงานอย่างไร
React.memo คือ HOC ที่ทำการ memoize คอมโพเนนต์เพื่อหลีกเลี่ยงการเรนเดอร์ใหม่หาก props ไม่เปลี่ยนแปลง
// Memoized component
const ExpensiveList = React.memo(function ExpensiveList({ items, onItemClick }) {
console.log('ExpensiveList rendered')
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onItemClick(item.id)}>
{item.name}
</li>
))}
</ul>
)
})
// Parent using the memoized component
function Parent() {
const [count, setCount] = useState(0)
const [items] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
])
// ❌ This function is recreated on every render
// So ExpensiveList re-renders despite memo
const handleClick = (id) => console.log(id)
// ✅ Stable function with useCallback
const handleClickStable = useCallback((id) => {
console.log(id)
}, [])
return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<ExpensiveList items={items} onItemClick={handleClickStable} />
</div>
)
}
// Custom comparison
const MemoWithCustomCompare = React.memo(
function Component({ user, onClick }) {
return <div onClick={onClick}>{user.name}</div>
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
return prevProps.user.id === nextProps.user.id
}
)17. Code splitting และ React.lazy คืออะไร
Code splitting แบ่งบันเดิลออกเป็น chunk ที่โหลดตามต้องการ ช่วยลดเวลาในการโหลดครั้งแรก
// Lazy loading components
const Dashboard = lazy(() => import('./Dashboard'))
const Settings = lazy(() => import('./Settings'))
const Profile = lazy(() => import('./Profile'))
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
)
}
// Conditional lazy loading
function FeaturePanel({ showAdvanced }) {
// Component is only loaded if needed
const AdvancedOptions = lazy(() => import('./AdvancedOptions'))
return (
<div>
<BasicOptions />
{showAdvanced && (
<Suspense fallback={<Skeleton />}>
<AdvancedOptions />
</Suspense>
)}
</div>
)
}
// Named exports with lazy
const { Chart } = lazy(() =>
import('./Charts').then(module => ({ default: module.Chart }))
)18. วิธีปรับแต่งรายการที่มีความยาวมาก
รายการที่ยาวมากอาจทำให้เกิดปัญหาด้านประสิทธิภาพ การทำ virtualization จะเรนเดอร์เฉพาะองค์ประกอบที่มองเห็นได้
// With react-window (virtualization library)
import { FixedSizeList } from 'react-window'
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style} className="list-item">
{items[index].name}
</div>
)
return (
<FixedSizeList
height={400}
width="100%"
itemCount={items.length}
itemSize={50}
>
{Row}
</FixedSizeList>
)
}
// Optimization without library: pagination
function PaginatedList({ items, pageSize = 20 }) {
const [page, setPage] = useState(0)
const paginatedItems = useMemo(() => {
const start = page * pageSize
return items.slice(start, start + pageSize)
}, [items, page, pageSize])
const totalPages = Math.ceil(items.length / pageSize)
return (
<div>
<ul>
{paginatedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div className="pagination">
<button
onClick={() => setPage(p => p - 1)}
disabled={page === 0}
>
Previous
</button>
<span>{page + 1} / {totalPages}</span>
<button
onClick={() => setPage(p => p + 1)}
disabled={page >= totalPages - 1}
>
Next
</button>
</div>
</div>
)
}Virtualization จะเริ่มมีประโยชน์เมื่อรายการมีองค์ประกอบเกินไม่กี่ร้อยรายการ สำหรับรายการที่เล็กกว่านั้น การใช้ pagination หรือการโหลดแบบทยอยมักเพียงพอแล้ว
19. วิธีหลีกเลี่ยงการเรนเดอร์ซ้ำที่ไม่จำเป็น
การระบุและกำจัดการเรนเดอร์ซ้ำที่ไม่จำเป็นเป็นสิ่งสำคัญต่อประสิทธิภาพ มีเทคนิคหลายอย่างที่ช่วยปรับการเรนเดอร์ได้
// Technique 1: Separate components
// ❌ Everything re-renders when count changes
function BadExample() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<ExpensiveComponent /> {/* Unnecessary re-render */}
</div>
)
}
// ✅ ExpensiveComponent no longer re-renders
function GoodExample() {
return (
<div>
<Counter />
<ExpensiveComponent />
</div>
)
}
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
// Technique 2: Pass children instead of rendering directly
// ✅ children is stable, no re-render of children
function ContextProvider({ children }) {
const [value, setValue] = useState(0)
return (
<Context.Provider value={value}>
{children} {/* children doesn't re-render when value changes */}
</Context.Provider>
)
}
// Technique 3: Use DevTools to identify re-renders
// React DevTools > Profiler > "Highlight updates when components render"20. Automatic batching ใน React 18+ คืออะไร
React 18 จะรวมการอัปเดต state โดยอัตโนมัติเพื่อลดจำนวนการเรนเดอร์ซ้ำ
// Before React 18: two re-renders
function handleClick() {
setCount(c => c + 1) // Re-render
setFlag(f => !f) // Re-render
}
// React 18+: single re-render (automatic batching)
function handleClick() {
setCount(c => c + 1) // Batched
setFlag(f => !f) // Batched
// Single re-render at the end
}
// Even in async callbacks (React 18 novelty)
async function handleSubmit() {
const response = await fetch('/api/submit')
// These two updates are batched
setData(response.data)
setLoading(false)
// Single re-render
}
// To force immediate re-render (rare)
import { flushSync } from 'react-dom'
function handleClick() {
flushSync(() => {
setCount(c => c + 1)
})
// DOM updated here
flushSync(() => {
setFlag(f => !f)
})
// DOM updated here
}พร้อมที่จะพิชิตการสัมภาษณ์ React / Next.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
React สมัยใหม่ (18+)
21. อธิบาย useTransition และ useDeferredValue
ฮุกทั้งสองนี้ใช้สำหรับทำเครื่องหมายว่าอัปเดตใดเป็นแบบไม่เร่งด่วน เพื่อรักษาให้อินเทอร์เฟซตอบสนองได้รวดเร็ว
// useTransition: mark an update as non-urgent
function SearchResults() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, startTransition] = useTransition()
const handleChange = (e) => {
// Urgent update: input stays responsive
setQuery(e.target.value)
// Non-urgent update: can be interrupted
startTransition(() => {
const filtered = filterLargeDataset(e.target.value)
setResults(filtered)
})
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <span>Searching...</span>}
<ul>
{results.map(r => <li key={r.id}>{r.name}</li>)}
</ul>
</div>
)
}
// useDeferredValue: defer a value
function DeferredSearch() {
const [query, setQuery] = useState('')
// deferredQuery is "behind" during rapid updates
const deferredQuery = useDeferredValue(query)
// Show indicator during the "lag"
const isStale = query !== deferredQuery
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{/* Uses deferred value, fewer calculations */}
<ExpensiveList filter={deferredQuery} />
</div>
</div>
)
}22. Suspense สำหรับการดึงข้อมูลทำงานอย่างไร
Suspense จัดการสถานะการโหลดในรูปแบบเชิงประกาศ และตั้งแต่ React 18+ สามารถใช้กับการดึงข้อมูลได้
function UserProfile({ userId }) {
return (
<Suspense fallback={<ProfileSkeleton />}>
<ProfileContent userId={userId} />
</Suspense>
)
}
// The component "suspends" during loading
function ProfileContent({ userId }) {
// This function suspends if data isn't ready
const user = useUserData(userId)
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
)
}
// Nested Suspense boundaries
function Dashboard() {
return (
<Suspense fallback={<DashboardSkeleton />}>
<Header />
<div className="grid">
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<Chart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentActivity />
</Suspense>
</div>
</Suspense>
)
}23. useActionState (React 19) คืออะไร
useActionState (ชื่อเดิม useFormState) ช่วยให้การจัดการฟอร์มร่วมกับ Server Action ง่ายขึ้น
'use server'
async function submitForm(prevState, formData) {
const email = formData.get('email')
const password = formData.get('password')
// Validation
if (!email || !password) {
return { error: 'All fields are required' }
}
try {
await createUser({ email, password })
return { success: true, message: 'Account created!' }
} catch (e) {
return { error: e.message }
}
}
// Component
function SignupForm() {
const [state, formAction, isPending] = useActionState(submitForm, {})
return (
<form action={formAction}>
{state.error && (
<div className="error">{state.error}</div>
)}
{state.success && (
<div className="success">{state.message}</div>
)}
<input name="email" type="email" placeholder="Email" />
<input name="password" type="password" placeholder="Password" />
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create account'}
</button>
</form>
)
}24. Server Component ทำงานอย่างไร
Server Component ทำงานบนเซิร์ฟเวอร์เท่านั้น ทำให้ลดปริมาณ JavaScript ที่ส่งไปยังไคลเอนต์ได้
// No "use client" = Server Component by default
import { prisma } from '@/lib/prisma'
// async/await directly in the component
export default async function ProductList() {
// Direct database call (no API)
const products = await prisma.product.findMany({
orderBy: { createdAt: 'desc' },
take: 10
})
return (
<div>
<h2>Recent Products</h2>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
{/* Can include Client Components */}
<AddToCartButton productId={product.id} />
</li>
))}
</ul>
</div>
)
}
// AddToCartButton.jsx
'use client'
// This component needs interactivity
export function AddToCartButton({ productId }) {
const [isPending, startTransition] = useTransition()
const handleClick = () => {
startTransition(async () => {
await addToCart(productId)
})
}
return (
<button onClick={handleClick} disabled={isPending}>
{isPending ? '...' : 'Add'}
</button>
)
}25. อธิบาย SSR streaming ใน React 18
SSR streaming จะส่ง HTML ไปยังเบราว์เซอร์อย่างค่อยเป็นค่อยไป แทนที่จะรอให้การเรนเดอร์เสร็จสมบูรณ์ก่อน
import { Suspense } from 'react'
export default function Page() {
return (
<div>
{/* Immediate render */}
<Header />
{/* Streamed when ready */}
<Suspense fallback={<MainContentSkeleton />}>
<MainContent />
</Suspense>
{/* Streamed independently */}
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
{/* Immediate render */}
<Footer />
</div>
)
}
// The browser receives:
// 1. HTML shell with Header, skeletons, Footer
// 2. MainContent when its request completes
// 3. Sidebar when its request completes
// loading.jsx for route-level streaming
export default function Loading() {
return <PageSkeleton />
}การจัดการ state
26. เมื่อใดควรใช้ Redux, Context API หรือทางเลือกอื่น
การเลือกขึ้นอยู่กับความซับซ้อนของ state และความต้องการของโปรเจกต์
// Context API: simple state, few updates
// ✅ Good for: theme, user, preferences
const ThemeContext = createContext()
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
// Redux / Zustand: complex state, frequent updates
// ✅ Good for: cart, filters, complex business data
import { create } from 'zustand'
const useCartStore = create((set, get) => ({
items: [],
total: 0,
addItem: (product) => set((state) => {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i
)
}
}
return { items: [...state.items, { ...product, quantity: 1 }] }
}),
removeItem: (id) => set((state) => ({
items: state.items.filter(i => i.id !== id)
})),
getTotal: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0)
}))
// React Query / SWR: server state (cache, refetch)
// ✅ Good for: API data, server synchronization
import { useQuery, useMutation } from '@tanstack/react-query'
function Products() {
const { data, isLoading } = useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then(r => r.json())
})
const mutation = useMutation({
mutationFn: (newProduct) => fetch('/api/products', {
method: 'POST',
body: JSON.stringify(newProduct)
})
})
}27. วิธีนำรูปแบบ Reducer ไปใช้
รูปแบบ Reducer ใช้รวมศูนย์ลอจิกการอัปเดต state ที่ซับซ้อนไว้ในที่เดียว
// Define actions
const ACTIONS = {
ADD_TODO: 'ADD_TODO',
TOGGLE_TODO: 'TOGGLE_TODO',
DELETE_TODO: 'DELETE_TODO',
SET_FILTER: 'SET_FILTER'
}
// Pure reducer (no side effects)
function todoReducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return {
...state,
todos: [
...state.todos,
{ id: Date.now(), text: action.payload, completed: false }
]
}
case ACTIONS.TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
}
case ACTIONS.DELETE_TODO:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
}
case ACTIONS.SET_FILTER:
return { ...state, filter: action.payload }
default:
return state
}
}
// Usage with useReducer
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: 'all'
})
const addTodo = (text) => {
dispatch({ type: ACTIONS.ADD_TODO, payload: text })
}
const toggleTodo = (id) => {
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id })
}
const filteredTodos = useMemo(() => {
switch (state.filter) {
case 'completed':
return state.todos.filter(t => t.completed)
case 'active':
return state.todos.filter(t => !t.completed)
default:
return state.todos
}
}, [state.todos, state.filter])
return (
<div>
<TodoForm onAdd={addTodo} />
<TodoList todos={filteredTodos} onToggle={toggleTodo} />
<FilterButtons
current={state.filter}
onChange={(f) => dispatch({ type: ACTIONS.SET_FILTER, payload: f })}
/>
</div>
)
}การทดสอบ
28. วิธีทดสอบคอมโพเนนต์ React
การทดสอบคอมโพเนนต์ใช้สำหรับตรวจสอบการเรนเดอร์และพฤติกรรมของอินเทอร์เฟซ
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Button from './Button'
describe('Button', () => {
// Basic rendering test
it('renders with correct text', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button')).toHaveTextContent('Click me')
})
// Interaction test
it('calls onClick when clicked', async () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click</Button>)
await userEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
// Disabled state test
it('does not call onClick when disabled', async () => {
const handleClick = jest.fn()
render(<Button disabled onClick={handleClick}>Click</Button>)
await userEvent.click(screen.getByRole('button'))
expect(handleClick).not.toHaveBeenCalled()
})
// Async state test
it('shows loading state', async () => {
render(<Button isLoading>Submit</Button>)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByTestId('spinner')).toBeInTheDocument()
})
})
// Testing a custom hook
import { renderHook, act } from '@testing-library/react'
import useCounter from './useCounter'
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter(0))
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
})29. วิธี mock การเรียก API ในการทดสอบ
Mock ช่วยแยกการทดสอบออกจากการพึ่งพาภายนอก
// With MSW (Mock Service Worker) - recommended
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
const server = setupServer(
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
])
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json()
return HttpResponse.json({ id: 3, ...body }, { status: 201 })
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('loads and displays users', async () => {
render(<UserList />)
expect(screen.getByText('Loading...')).toBeInTheDocument()
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument()
expect(screen.getByText('Bob')).toBeInTheDocument()
})
})
// Override for a specific test
test('handles error state', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ error: 'Server error' }, { status: 500 })
})
)
render(<UserList />)
await waitFor(() => {
expect(screen.getByText('Loading error')).toBeInTheDocument()
})
})30. วิธีจัดโครงสร้างการทดสอบเพื่อความครอบคลุมที่เหมาะสม
กลยุทธ์การทดสอบที่สมดุลควรรวมการทดสอบในระดับต่างๆ เข้าด้วยกัน
// Recommended testing pyramid:
// - Many unit tests (fast, isolated)
// - Some integration tests (components + hooks)
// - Few E2E tests (critical scenarios)
// Unit tests: pure logic
// utils/formatPrice.test.js
describe('formatPrice', () => {
it('formats with 2 decimals', () => {
expect(formatPrice(10)).toBe('$10.00')
})
it('handles zero', () => {
expect(formatPrice(0)).toBe('$0.00')
})
})
// Integration tests: complete component
// features/Checkout/Checkout.test.jsx
describe('Checkout', () => {
it('completes purchase flow', async () => {
render(
<CartProvider>
<Checkout />
</CartProvider>
)
// Fill the form
await userEvent.type(screen.getByLabelText('Email'), 'test@example.com')
await userEvent.type(screen.getByLabelText('Card'), '4242424242424242')
// Submit
await userEvent.click(screen.getByRole('button', { name: 'Pay' }))
// Verify result
await waitFor(() => {
expect(screen.getByText('Order confirmed')).toBeInTheDocument()
})
})
})
// E2E tests with Playwright
// e2e/checkout.spec.ts
test('user can complete checkout', async ({ page }) => {
await page.goto('/products')
await page.click('[data-testid="add-to-cart-1"]')
await page.click('[data-testid="checkout-button"]')
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="card"]', '4242424242424242')
await page.click('button[type="submit"]')
await expect(page.locator('text=Order confirmed')).toBeVisible()
})บทสรุป
คำถามทั้ง 30 ข้อนี้ครอบคลุมความรู้ React ที่จำเป็นสำหรับการสัมภาษณ์ ขอบเขตหลักที่ควรเชี่ยวชาญ ได้แก่
- ✅ พื้นฐาน: Virtual DOM, JSX, props กับ state, คอมโพเนนต์
- ✅ Hooks: useState, useEffect, useMemo, useCallback, useRef, useContext
- ✅ รูปแบบ: HOC, Render Props, Compound Components, custom hooks
- ✅ ประสิทธิภาพ: React.memo, lazy loading, virtualization
- ✅ React สมัยใหม่: Suspense, Transitions, Server Components
- ✅ การจัดการ state: Context, Redux/Zustand, React Query
- ✅ การทดสอบ: React Testing Library, mocks, กลยุทธ์การทดสอบ
การเตรียมตัวสัมภาษณ์ React ไม่ได้เป็นเพียงการท่องจำ การฝึกฝนจากโปรเจกต์จริงช่วยให้แนวคิดเหล่านี้ฝังแน่น และอธิบายได้อย่างเป็นธรรมชาติในระหว่างการสัมภาษณ์
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

React 19: Server Components ในระบบ Production - คู่มือฉบับสมบูรณ์
เชี่ยวชาญ React 19 Server Components ในระบบ Production สถาปัตยกรรม รูปแบบการออกแบบ Streaming Caching และการเพิ่มประสิทธิภาพสำหรับแอปพลิเคชันที่มีประสิทธิภาพสูง

คำถามสัมภาษณ์ Backend Node.js: คู่มือฉบับสมบูรณ์ 2026
25 คำถามสัมภาษณ์ Backend Node.js ที่พบบ่อยที่สุด Event loop, async/await, streams, clustering และประสิทธิภาพอธิบายพร้อมคำตอบโดยละเอียด

25 คำถามสัมภาษณ์งาน Laravel และ PHP ยอดนิยมในปี 2026
รวม 25 คำถามสัมภาษณ์งาน Laravel และ PHP ที่พบบ่อยที่สุด ครอบคลุม Service Container, Eloquent ORM, middleware, queues และการ deploy ระบบ production พร้อมคำตอบและตัวอย่างโค้ดครบถ้วน