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 ในปี 2026: Vitest, React Testing Library และ Best Practices
เชี่ยวชาญการทดสอบ React ด้วย Vitest และ React Testing Library เรียนรู้รูปแบบการทดสอบ component การจัดการ async กลยุทธ์ mocking และ best practices สำหรับการสัมภาษณ์ 2026

Zustand กับ Redux Toolkit ในปี 2026: เลือกตัวจัดการ state ของ React ตัวไหนดี?
เปรียบเทียบเชิงปฏิบัติปี 2026 ระหว่าง Zustand 5 กับ Redux Toolkit 2.x สำหรับการจัดการ state ของ React ทั้งการติดตั้ง การดึงข้อมูล async ด้วย RTK Query ประสิทธิภาพ และคำถามสัมภาษณ์

Next.js 16 Server Actions ปี 2026: Mutation, Revalidation และคำถามสัมภาษณ์
Next.js 16 Server Actions จัดการ mutation, revalidation, สถานะ pending, optimistic UI และความปลอดภัยอย่างไร พร้อมคำถามสัมภาษณ์ที่ทดสอบแต่ละแนวคิด