Table of Contents
- What is React and why is it used?
- What is the Virtual DOM?
- What are Components in React?
- What is JSX?
- Props vs State
- What are React Hooks?
- useState Hook
- useEffect Hook
- useRef Hook
- useMemo vs useCallback
- useReducer Hook
- useContext Hook
- Custom Hooks
- What is Prop Drilling?
- React Context vs Redux
- What are Keys in React?
- What are Fragments?
- Controlled vs Uncontrolled Components
- What is Lifting State Up?
- What are Error Boundaries?
- React.memo and Memoization
- What is Code Splitting?
- What are Higher-Order Components?
- What are React Portals?
- Suspense and Lazy Loading
- What is the useEffect cleanup function?
- What are Synthetic Events?
- How does React handle forms?
- What is Server-Side Rendering (SSR)?
- React性能优化技巧
1. What is React and why is it used?
React is an open-source JavaScript library developed by Meta (Facebook) for building user interfaces, particularly single-page applications (SPAs). It allows developers to create large web applications that can change data without reloading the page.
Why use React?
- Component-based architecture: Build encapsulated components that manage their own state, then compose them to make complex UIs
- Virtual DOM: Efficiently updates and renders only the components that change, improving performance
- Declarative: Describe what the UI should look like for each state, and React handles the DOM updates
- Unidirectional data flow: Data flows one way (parent to child), making state changes predictable and debuggable
- Rich ecosystem: Massive community, thousands of libraries, and strong tooling support
- React Native: Same paradigm for building mobile apps on iOS and Android
Interview tip: Don't just say "it's a JavaScript framework." React is a library, not a framework. It handles the view layer and can be augmented with other libraries for routing, state management, etc.
2. What is the Virtual DOM?
The Virtual DOM is a lightweight JavaScript representation of the actual DOM (Document Object Model). It acts as a copy of the real DOM kept in memory and is used by React to optimize rendering performance.
How it works (the Reconciliation process):
- When state changes, React creates a new Virtual DOM tree
- React compares (diffs) the new Virtual DOM with the previous one
- React identifies the minimal set of changes needed
- Only those specific changes are applied to the real DOM
// React batches updates and only touches the real DOM where needed
// Without Virtual DOM: re-render entire list on every change
// With Virtual DOM: only re-render the specific item that changed
// This is why React is fast — it minimizes expensive DOM operations
// Real DOM manipulation is 10-100x slower than JavaScript operations
Key insight: The Virtual DOM isn't inherently faster than the real DOM. React's advantage is in knowing when to update and what to update, minimizing unnecessary DOM manipulations.
3. What are Components in React?
Components are independent, reusable pieces of UI. They accept inputs (props) and return React elements describing what should appear on the screen.
Functional Components (Modern Approach)
// Functional Component with props
function UserCard({ name, email, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Arrow function variant
const UserCard = ({ name, email, avatar }) => (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
Class Components (Legacy)
import { Component } from 'react';
class UserCard extends Component {
render() {
const { name, email, avatar } = this.props;
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
}
Best practice: Always use functional components with Hooks. Class components are still valid but are considered legacy. Functional components are simpler, easier to test, and have better performance.
4. What is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like code inside JavaScript. It makes creating React element trees more readable and expressive.
// JSX is syntactic sugar for React.createElement()
// JSX:
const element = <h1 className="title">Hello, {name}!</h1>
// Equivalent without JSX:
const element = React.createElement(
'h1',
{ className: 'title' },
`Hello, ${name}!`
);
// JSX supports expressions inside curly braces {}
const greeting = <p>Current time: {new Date().toLocaleTimeString()}</p>
// JSX supports conditional rendering
const display = <div>{isLoggedIn ? <LogoutButton /> : <LoginButton />}</div>
// JSX supports mapping arrays
const list = (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
JSX Rules
- Return a single root element (use Fragment
<>...</>if needed) - All tags must be closed (including self-closing like
<img />) - Use
classNameinstead ofclass - Use
htmlForinstead offor - Inline styles use objects:
style={{ color: 'red' }}
5. Props vs State
Understanding the difference between props and state is fundamental to React development.
| Feature | Props | State |
|---|---|---|
| Definition | Read-only inputs passed from parent to child | Mutable data managed within the component |
| Mutability | Immutable — cannot be modified by the receiving component | Mutable — updated via setState or useState |
| Source | Parent component | Component itself |
| Triggers re-render? | Only when parent re-renders | Yes, whenever state changes |
| Use case | Configuration, data passing, callbacks | Interactivity, dynamic data, form inputs |
// Props: passed from parent
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
}
// Child receives props (read-only)
function Child({ count, onIncrement }) {
// Props cannot be modified: this.props.count++ // WRONG
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>+1</button>
</div>
);
}
// State: managed internally
function Counter() {
const [count, setCount] = useState(0); // state
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
6. What are React Hooks?
Hooks are functions that let you "hook into" React state and lifecycle features from functional components. They were introduced in React 16.8 to eliminate the need for class components.
Rules of Hooks
- Only call Hooks at the top level — never inside loops, conditions, or nested functions
- Only call Hooks from React functions — either React function components or custom Hooks
// ✅ Correct: Hooks at top level
function MyComponent() {
const [count, setCount] = useState(0);
const ref = useRef(null);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <button ref={ref}>{count}</button>;
}
// ❌ Wrong: Hook inside condition
function MyComponent({ shouldFetch }) {
if (shouldFetch) {
const [data, setData] = useState(null); // ❌ WRONG!
}
}
7. useState Hook
useState is the most basic Hook. It declares a state variable that persists between re-renders.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// count: current state value
// setCount: function to update state
// Functional update (recommended when new state depends on old state)
const increment = () => setCount(prev => prev + 1);
// Object state
const [user, setUser] = useState({ name: '', email: '' });
const updateUser = () => {
setUser(prev => ({ ...prev, name: 'John' })); // spread previous state
// setUser({ name: 'John' }); // ❌ WRONG: loses email field
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<input
value={user.name}
onChange={(e) => setUser(prev => ({ ...prev, name: e.target.value }))}
/>
</div>
);
}
Common mistake: Always use the functional updater form setCount(prev => prev + 1) when the new state depends on the previous state. Direct access setCount(count + 1) can cause stale state bugs due to closures.
8. useEffect Hook
useEffect lets you perform side effects in functional components — data fetching, subscriptions, DOM manipulation, timers, and more.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Runs after every render where userId changes
useEffect(() => {
let cancelled = false; // cleanup flag
const fetchUser = async () => {
setLoading(true);
try {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!cancelled) setUser(data);
} catch (err) {
console.error(err);
} finally {
if (!cancelled) setLoading(false);
}
};
fetchUser();
// Cleanup function: runs before next effect or on unmount
return () => { cancelled = true; };
}, [userId]); // dependency array
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
// useEffect with different dependency patterns:
useEffect(() => {
// Runs after EVERY render (no dependency array)
});
useEffect(() => {
// Runs only ONCE on mount (empty dependency array)
}, []);
useEffect(() => {
// Runs when 'userId' changes
}, [userId]);
useEffect(() => {
// Runs when 'userId' OR 'theme' changes
}, [userId, theme]);
Common useEffect Patterns
| Pattern | Dependency Array | When It Runs |
|---|---|---|
| On every render | Not provided | After every render |
| On mount only | [] |
Once after initial render |
| On value change | [value] |
After render when value changes |
| Cleanup only | N/A | Return function runs before next effect |
9. useRef Hook
useRef creates a mutable reference object whose .current property persists across renders without triggering re-renders when changed.
Use Cases
- Accessing DOM elements directly
- Storing previous values
- Holding mutable values that don't cause re-renders
- Storing timer IDs
import { useRef, useEffect, useState } from 'react';
function TextInput() {
const inputRef = useRef(null);
const prevCountRef = useRef(0);
const [count, setCount] = useState(0);
// Focus input on mount
useEffect(() => {
inputRef.current.focus();
}, []);
// Track previous count without re-rendering
useEffect(() => {
prevCountRef.current = count;
}, [count]);
return (
<div>
<input ref={inputRef} placeholder="Type here..." />
<p>Current: {count}, Previous: {prevCountRef.current}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
Key difference from useState: Changing a useRef value does NOT cause a re-render. Use useRef for values that need to persist but shouldn't trigger UI updates.
10. useMemo vs useCallback
Both are performance optimization hooks, but they serve different purposes.
| Feature | useMemo | useCallback |
|---|---|---|
| Purpose | Memoizes a computed value | Memoizes a function |
| Returns | The memoized value | The memoized function |
| Use case | Expensive calculations | Stable function references for child components |
| Equivalent to | useCallback(() => value, [deps]) |
useMemo(() => fn, [deps]) |
import { useMemo, useCallback, useState } from 'react';
function ProductList({ products }) {
const [filter, setFilter] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
// useMemo: expensive calculation — only re-runs when dependencies change
const filteredProducts = useMemo(() => {
console.log('Filtering products...'); // only logs when filter/sort changes
return products
.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => sortOrder === 'asc' ? a.price - b.price : b.price - a.price);
}, [products, filter, sortOrder]);
// useCallback: stable function reference — prevents child re-renders
const handleAddToCart = useCallback((productId) => {
console.log(`Adding product ${productId} to cart`);
}, []);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
);
}
Don't over-optimize: Only use useMemo and useCallback when you've identified a performance bottleneck. Premature optimization adds complexity without benefit.
11. useReducer Hook
useReducer is an alternative to useState for complex state logic. It's similar to Redux patterns and is ideal when state transitions depend on the previous state.
import { useReducer } from 'react';
// 1. Define reducer function
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, {
id: Date.now(),
text: action.payload,
completed: false
}];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
};
function TodoApp() {
// 2. Initialize useReducer
const [todos, dispatch] = useReducer(todoReducer, []);
// 3. Dispatch actions
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text });
};
return (
<div>
<input
onKeyDown={(e) => {
if (e.key === 'Enter') {
addTodo(e.target.value);
e.target.value = '';
}
}}
/>
{todos.map(todo => (
<div key={todo.id}>
<span
onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
✕
</button>
</div>
))}
</div>
);
}
12. useContext Hook
useContext provides a way to pass data through the component tree without manually passing props at every level (solving prop drilling).
import { createContext, useContext, useState } from 'react';
// 1. Create context
const ThemeContext = createContext(null);
// 2. Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
const toggleTheme = () => setTheme(prev =>
prev === 'dark' ? 'light' : 'dark'
);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Consumer component (any depth)
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
className={`btn-${theme}`}
>
Current theme: {theme}
</button>
);
}
// 4. Wrap app with provider
function App() {
return (
<ThemeProvider>
<Header /> {/* ThemedButton can be anywhere in the tree */}
</ThemeProvider>
);
}
Performance note: When a context value changes, all consumers re-render. For performance-critical scenarios, split contexts or use useMemo on the provider value.
13. Custom Hooks
Custom Hooks let you extract component logic into reusable functions. A custom Hook is simply a JavaScript function whose name starts with "use" and that may call other Hooks.
// Custom Hook: useLocalStorage
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Custom Hook: useFetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Usage in any component
function UserProfile() {
const [name, setName] = useLocalStorage('user-name', '');
const { data: user, loading } = useFetch(`/api/user/${name}`);
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
14. What is Prop Drilling?
Prop drilling occurs when you pass data through multiple layers of components that don't need the data, just to reach a deeply nested child that does.
// ❌ Prop drilling example: theme passes through 3 unnecessary layers
function App() {
const [theme, setTheme] = useState('dark');
return <Layout theme={theme} />; // Layout doesn't use theme
}
function Layout({ theme }) {
return <Sidebar theme={theme} />; // Sidebar doesn't use theme
}
function Sidebar({ theme }) {
return <ThemedButton theme={theme} />; // This component needs theme
}
// ✅ Solution: Use Context
function App() {
return (
<ThemeProvider>
<Layout /> {/* No theme prop needed! */}
</ThemeProvider>
);
}
function ThemedButton() {
const { theme } = useContext(ThemeContext); // Direct access
return <button className={`btn-${theme}`}>Toggle</button>;
}
15. React Context vs Redux
Both manage global state, but they serve different use cases.
| Feature | React Context | Redux |
|---|---|---|
| Purpose | Lightweight global state | Complex state management |
| Learning curve | Low | Medium-High |
| DevTools | None built-in | Excellent (Redux DevTools) |
| Performance | Re-renders all consumers | Selective subscriptions |
| Best for | Theme, auth, locale | Large apps with complex state |
| Middleware | Not built-in | Redux Thunk, Redux Saga |
Modern recommendation: Start with React Context for simple global state. If you need middleware, time-travel debugging, or have highly frequent updates, consider Redux Toolkit (RTK).
16. What are Keys in React?
Keys help React identify which items in a list have changed, been added, or removed. They give elements a stable identity across renders.
// ✅ Correct: Using unique, stable IDs
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// ❌ Wrong: Using index as key (causes bugs with reordering/deletion)
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li> // BAD!
))}
// ❌ Wrong: Using Math.random() as key (creates new key every render)
{todos.map(todo => (
<li key={Math.random()}>{todo.text}</li> // VERY BAD!
))}
When is index as key acceptable?
- The list is static and will not be reordered or filtered
- The list is generated from a non-deterministic source (like user input)
- Items don't have unique IDs
17. What are Fragments?
Fragments let you group multiple elements without adding extra DOM nodes. They solve the "multiple root elements" JSX limitation.
// ❌ Problem: Can't return two elements without a wrapper
function Header() {
return (
// <div> adds an unnecessary DOM node
<div>
<h1>Title</h1>
<p>Subtitle</p>
</div>
);
}
// ✅ Solution 1: Short syntax
function Header() {
return (
<>
<h1>Title</h1>
<p>Subtitle</p>
</>
);
}
// ✅ Solution 2: Full syntax (needed when passing key prop in lists)
function Header() {
return (
<Fragment>
<h1>Title</h1>
<p>Subtitle</p>
</Fragment>
);
}
18. Controlled vs Uncontrolled Components
This distinction matters primarily for form elements and how React manages their state.
| Feature | Controlled | Uncontrolled |
|---|---|---|
| State source | React state (component) | DOM itself (ref) |
| Data flow | Two-way binding | Read from DOM when needed |
| Validation | On every keystroke | On submit |
| Best for | Most forms, real-time validation | Simple forms, file inputs |
// ✅ Controlled Component (recommended)
function ControlledForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Login</button>
</form>
);
}
// ⚡ Uncontrolled Component (use ref for quick reads)
function UncontrolledForm() {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log({
email: emailRef.current.value,
password: passwordRef.current.value,
});
};
return (
<form onSubmit={handleSubmit}>
<input type="email" ref={emailRef} defaultValue="" required />
<input type="password" ref={passwordRef} defaultValue="" required />
<button type="submit">Login</button>
</form>
);
}
19. What is Lifting State Up?
Lifting state up is the pattern of moving shared state to the nearest common ancestor of components that need it. Instead of syncing state between siblings, the parent holds the state and passes it down.
// Two sibling components sharing state
function TemperatureConverter() {
const [celsius, setCelsius] = useState('');
// State lives in parent, shared with both children
return (
<div>
<TemperatureInput
scale="celsius"
value={celsius}
onChange={setCelsius}
/>
<TemperatureInput
scale="fahrenheit"
value={toFahrenheit(celsius)}
onChange={(f) => setCelsius(toCelsius(f))}
/>
<BoilingVerdict celsius={parseFloat(celsius)} />
</div>
);
}
function TemperatureInput({ scale, value, onChange }) {
return (
<fieldset>
<legend>Enter temperature in {scale}:</legend>
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</fieldset>
);
}
20. What are Error Boundaries?
Error Boundaries are React components that catch JavaScript errors in their child component tree, log those errors, and display a fallback UI instead of crashing the entire app.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
// Log to error reporting service
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try Again
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
Note: Error Boundaries do NOT catch errors in event handlers, async code, server-side rendering, or errors in the boundary itself. Use try-catch in event handlers instead.
21. React.memo and Memoization
React.memo is a higher-order component that wraps a functional component and prevents it from re-rendering if its props haven't changed (shallow comparison).
import { memo, useState } from 'react';
// This component will only re-render if its props change
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
console.log('ExpensiveList rendered'); // Only logs when items change
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onSelect(item.id)}>
{item.name}
</li>
))}
</ul>
);
});
// Custom comparison function for deeper control
const MemoizedComponent = memo(
function MyComponent({ user, settings }) {
return <div>{user.name} - {settings.theme}</div>;
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
return prevProps.user.id === nextProps.user.id
&& prevProps.settings.theme === nextProps.settings.theme;
}
);
When to use React.memo: When a component renders often with the same props, or when it has expensive rendering logic. Don't wrap every component — it adds overhead.
22. What is Code Splitting?
Code splitting breaks your app into smaller bundles that load on demand, reducing the initial page load time.
import { lazy, Suspense } from 'react';
// Lazy load components — only loaded when needed
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// Preload on hover for instant navigation
function NavLink({ to, children }) {
const preload = () => {
if (to === '/dashboard') import('./pages/Dashboard');
else if (to === '/settings') import('./pages/Settings');
};
return (
<Link to={to} onMouseEnter={preload}>
{children}
</Link>
);
}
23. What are Higher-Order Components (HOC)?
A Higher-Order Component is a function that takes a component and returns a new enhanced component. HOCs are used for cross-cutting concerns like authentication, logging, or data fetching.
// HOC: Adds loading state to any component
function withLoading(WrappedComponent) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <div className="spinner">Loading...</div>;
return <WrappedComponent {...props} />;
};
}
// HOC: Adds authentication check
function withAuth(WrappedComponent) {
return function WithAuthComponent(props) {
const { user, loading } = useAuth();
if (loading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
// Usage
const UserDashboard = withAuth(withLoading(Dashboard));
// Or compose multiple HOCs
const EnhancedComponent = compose(
withAuth,
withLoading,
withLogger
)(Dashboard);
Modern alternative: Custom Hooks are now preferred over HOCs for most use cases. HOCs are still valid but can cause wrapper hell and make debugging harder.
24. What are React Portals?
Portals let you render children into a DOM node that exists outside the parent component's DOM hierarchy. Useful for modals, tooltips, and dropdowns that need to escape overflow constraints.
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>✕</button>
{children}
</div>
</div>,
document.getElementById('modal-root') // Render outside #app
);
}
// In index.html:
// <div id="root"></div>
// <div id="modal-root"></div>
25. Suspense and Lazy Loading
Suspense lets you show a fallback while waiting for something to load — lazy components, data, or images.
import { Suspense, lazy } from 'react';
// Lazy loaded component
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
{/* Suspense boundary shows fallback while HeavyComponent loads */}
<Suspense fallback={<div className="skeleton">Loading...</div>}>
<HeavyComponent />
</Suspense>
{/* Nested Suspense boundaries for different sections */}
<Suspense fallback={<Spinner />}>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>
</div>
);
}
26. What is the useEffect cleanup function?
The cleanup function returned from useEffect runs before the effect re-fires and when the component unmounts. It's essential for preventing memory leaks.
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
// Cleanup: runs when roomId changes or component unmounts
return () => {
connection.disconnect();
};
}, [roomId]);
return <div>Welcome to room {roomId}!</div>;
}
// Timer example
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup: clear interval on unmount
return () => clearInterval(interval);
}, []);
return <p>Elapsed: {seconds}s</p>;
}
// Subscription example
function WindowSize() {
const [size, setSize] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setSize(window.innerWidth);
window.addEventListener('resize', handleResize);
// Cleanup: remove event listener
return () => window.removeEventListener('resize', handleResize);
}, []);
return <p>Window width: {size}px</p>;
}
27. What are Synthetic Events?
React's SyntheticEvent is a cross-browser wrapper around the browser's native event. It normalizes event handling so events work consistently across all browsers.
function ClickHandler() {
// Synthetic Event
const handleClick = (e) => {
e.preventDefault(); // Works cross-browser
e.stopPropagation(); // Works cross-browser
console.log('Target:', e.target); // Native DOM element
console.log('Current Target:', e.currentTarget); // Element with handler
console.log('Event Type:', e.type); // 'click'
// Access native event if needed
console.log('Native event:', e.nativeEvent);
};
return <button onClick={handleClick}>Click me</button>;
}
// Passing event data
function ListItem({ item, onDelete }) {
const handleDelete = (e, id) => {
e.stopPropagation();
onDelete(id);
};
return (
<li>
{item.name}
<button onClick={(e) => handleDelete(e, item.id)}>Delete</button>
</li>
);
}
28. How does React handle forms?
React handles forms through controlled components where form data is handled by React state, or uncontrolled components where form data is handled by the DOM.
// Complete form example with validation
function RegistrationForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.name.trim()) newErrors.name = 'Name is required';
if (!formData.email.includes('@')) newErrors.email = 'Invalid email';
if (formData.password.length < 8) newErrors.password = 'Min 8 characters';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// Clear error when user starts typing
if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' }));
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('Submitting:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input name="name" value={formData.name} onChange={handleChange} />
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<input name="email" value={formData.email} onChange={handleChange} />
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input name="password" type="password" value={formData.password} onChange={handleChange} />
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit">Register</button>
</form>
);
}
29. What is Server-Side Rendering (SSR)?
SSR renders React components on the server and sends fully rendered HTML to the client. Next.js is the most popular React framework for SSR.
| Feature | CSR (Client-Side) | SSR (Server-Side) | SSG (Static) |
|---|---|---|---|
| Rendering | In browser | On server per request | At build time |
| SEO | Poor (content not in HTML) | Excellent | Excellent |
| Performance | Slow initial load | Fast first paint | Fastest |
| Best for | SPAs, dashboards | Dynamic content | Blogs, docs, marketing |
Next.js App Router uses React Server Components by default, which render on the server and send zero JavaScript to the client for those components — combining the best of SSR and SSG.
30. React性能优化技巧
Here are the top performance optimization techniques every React developer should know.
1. Avoid Unnecessary Re-renders
// ❌ Creating new objects/functions on every render
function Parent() {
return (
<Child
style={{ color: 'red' }} // new object every render!
onClick={() => doSomething()} // new function every render!
/>
);
}
// ✅ Memoize objects and functions
const style = { color: 'red' };
const handleClick = useCallback(() => doSomething(), []);
function Parent() {
return <Child style={style} onClick={handleClick} />;
}
2. Use React.memo for Pure Components
// Only re-renders when props change
const ListItem = memo(function ListItem({ item, onDelete }) {
return (
<li>
{item.name}
<button onClick={() => onDelete(item.id)}>Delete</button>
</li>
);
});
3. Virtualize Long Lists
// Only render visible items (use react-window or react-virtualized)
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={500}
width="100%"
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
}
4. Lazy Load Heavy Components
const Chart = lazy(() => import('./Chart'));
const DataTable = lazy(() => import('./DataTable'));
// Only load when needed
<Suspense fallback={<Spinner />}>
{showChart && <Chart data={data} />}
</Suspense>
5. Profile and Measure
// Use React DevTools Profiler to identify bottlenecks
// Use Chrome Performance tab to measure actual render time
// Use Why Did You Render (wdyr) library to trace unnecessary renders
// Install: npm install @welldone-software/why-did-you-render
import React from 'react';
import wdyr from '@welldone-software/why-did-you-render';
if (process.env.NODE_ENV === 'development') {
wdyr(React);
}
Quick Checklist
- Use
React.memofor components that receive stable props - Use
useCallbackfor functions passed as props - Use
useMemofor expensive computations - Avoid creating objects/arrays inline in JSX
- Use lazy loading and code splitting
- Virtualize long lists (100+ items)
- Keep state as close to where it's used as possible
- Use production build for performance testing
Final tip: Always measure before optimizing. Use console.time(), React DevTools Profiler, and Chrome Lighthouse to identify actual bottlenecks before applying optimizations.
Final Thoughts
React interviews test more than just knowledge — they assess your understanding of why things work the way they do. Focus on understanding the concepts behind these answers rather than memorizing them.
Key Takeaways
- Master the fundamentals: JSX, components, props, state, and the Virtual DOM
- Understand Hooks deeply — especially useState, useEffect, useRef, and useContext
- Know when to use Context vs Redux vs local state
- Practice writing custom Hooks and explaining their benefits
- Be comfortable with performance optimization patterns
- Understand server-side rendering concepts for modern React apps
Practice makes perfect: Build projects using these concepts. The best way to prepare for React interviews is to actually build React apps and explain your decisions along the way.