Teknisk Intervju: Frontend-ramverk med React
Detta avsnitt innehåller vanliga tekniska intervjufrågor för React-utveckling. Frågorna täcker React fundamentals, komponenter, hooks, state management, routing och prestanda.
Fråga 1: React Grundläggande Koncept
Intervjuare: "Förklara vad React är och vad Virtual DOM innebär. Visa skillnaden mellan JSX och vanlig HTML."
Bra svar:
// React är ett JavaScript-bibliotek för att bygga användargränssnitt
// Virtual DOM är en representation av den riktiga DOM:en i minnet
// JSX - JavaScript XML (React syntax)
function Welcome({ name }) {
return (
<div className="greeting">
<h1>Hej {name}!</h1>
<p>Dagens datum: {new Date().toLocaleDateString()}</p>
</div>
);
}
// Motsvarande HTML skulle vara statisk:
// <div class="greeting">
// <h1>Hej Anna!</h1>
// <p>Dagens datum: 2024-01-15</p>
// </div>
// Skillnader JSX vs HTML:
// - className istället för class
// - {} för JavaScript-uttryck
// - camelCase för attribut (onClick, onChange)
// - Måste ha en parent element eller React Fragment
Förklaring: Virtual DOM gör att React kan jämföra förändringar effektivt och bara uppdatera de delar av DOM:en som ändrats. JSX tillåter JavaScript-uttryck inbäddade i markup.
Fråga 2: Funktionella vs Class Components
Intervjuare: "Vad är skillnaden mellan funktionella och class components? Varför föredras funktionella komponenter idag?"
Bra svar:
// Class Component (äldre sätt)
class ClassCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('Component mounted');
}
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}
// Functional Component med hooks (modernt sätt)
function FunctionalCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component mounted/updated');
}, []);
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
Fördelar med funktionella komponenter:
- Enklare syntax och mindre boilerplate-kod
- Bättre prestanda med hooks
- Lättare att testa och förstå
- Modern React development standard
Fråga 3: useState och useEffect Hooks
Intervjuare: "Förklara hur useState och useEffect fungerar. Visa ett exempel med API-anrop."
Bra svar:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
// useState för state management
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// useEffect för side effects (API calls, subscriptions etc)
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('User not found');
}
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Dependency array - kör när userId ändras
// Cleanup effect (körs när component unmounts)
useEffect(() => {
return () => {
console.log('Cleanup when component unmounts');
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
useEffect dependency arrays:
[]
- körs bara en gång (componentDidMount)[userId]
- körs när userId ändras- Ingen array - körs efter varje render
Fråga 4: Props och State Management
Intervjuare: "Förklara skillnaden mellan props och state. Visa hur man hanterar props drilling och Context API."
Bra svar:
// Props - data som skickas från parent till child
function Parent() {
const [theme, setTheme] = useState('light');
return (
<Child
theme={theme}
onThemeChange={setTheme}
title="My App"
/>
);
}
function Child({ theme, onThemeChange, title }) {
return (
<div className={`app ${theme}`}>
<h1>{title}</h1>
<button onClick={() => onThemeChange(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
// Context API för att undvika props drilling
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Använda context
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
className={`btn btn-${theme}`}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
Current theme: {theme}
</button>
);
}
// App med Context Provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<ThemedButton />
</ThemeProvider>
);
}
State vs Props:
- State: Intern data som komponenten äger och kan ändra
- Props: Data som skickas från föräldrakomponent, read-only
Fråga 5: Event Handling och Forms
Intervjuare: "Visa hur man hanterar formulär och events i React på ett kontrollerat sätt."
Bra svar:
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
subscribe: false
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
// Hantera input changes
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
// Rensa fel när användaren börjar skriva
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
// Validering
const validateForm = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Namn är obligatoriskt';
}
if (!formData.email.trim()) {
newErrors.email = 'E-post är obligatoriskt';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Ogiltig e-postadress';
}
if (!formData.message.trim()) {
newErrors.message = 'Meddelande är obligatoriskt';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// Hantera form submission
const handleSubmit = async (e) => {
e.preventDefault();
if (!validateForm()) return;
setIsSubmitting(true);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
alert('Meddelande skickat!');
setFormData({ name: '', email: '', message: '', subscribe: false });
} else {
throw new Error('Något gick fel');
}
} catch (error) {
alert('Fel vid sändning: ' + error.message);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Namn:</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className={errors.name ? 'error' : ''}
/>
{errors.name && <span className="error-text">{errors.name}</span>}
</div>
<div>
<label htmlFor="email">E-post:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error-text">{errors.email}</span>}
</div>
<div>
<label htmlFor="message">Meddelande:</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
className={errors.message ? 'error' : ''}
/>
{errors.message && <span className="error-text">{errors.message}</span>}
</div>
<div>
<label>
<input
type="checkbox"
name="subscribe"
checked={formData.subscribe}
onChange={handleChange}
/>
Prenumerera på nyhetsbrev
</label>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Skickar...' : 'Skicka'}
</button>
</form>
);
}
Viktiga principer för formulär i React:
- Kontrollerade komponenter (controlled components)
- Hantera all state i React
- Validering både på input och submit
- Förhindra default form behavior med
preventDefault()
Fråga 6: Lists och Keys
Intervjuare: "Förklara varför keys är viktiga i React lists och visa best practices."
Bra svar:
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Handla mat', completed: false },
{ id: 2, text: 'Träna', completed: true },
{ id: 3, text: 'Studera React', completed: false }
]);
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id} // ✅ Använd stabil, unik key
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
);
}
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className={todo.completed ? 'completed' : ''}>
<span onClick={() => onToggle(todo.id)}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>
Ta bort
</button>
</li>
);
}
// ❌ Dåliga exempel på keys:
// key={index} - problem vid omordning eller radering
// key={Math.random()} - skapar nya keys vid varje render
// key={todo.text} - problem om text ändras eller är duplicerad
// ✅ Bra keys:
// key={todo.id} - stabil unik identifierare
// key={`${todo.userId}-${todo.timestamp}`} - sammansatt unik key
Varför keys är viktiga:
- React använder keys för att identifiera vilka element som ändrats
- Förbättrar prestanda vid re-rendering
- Förhindrar buggar med component state
Fråga 7: React Router Navigation
Intervjuare: "Implementera routing med React Router inklusive protected routes."
Bra svar:
import { BrowserRouter, Routes, Route, Navigate, Link, useNavigate } from 'react-router-dom';
// Protected Route Component
function ProtectedRoute({ children }) {
const isAuthenticated = useAuth(); // Custom hook för auth
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
// Navigation Component
function Navigation() {
const { user, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate('/');
};
return (
<nav>
<Link to="/">Hem</Link>
<Link to="/about">Om oss</Link>
{user ? (
<>
<Link to="/dashboard">Dashboard</Link>
<Link to="/profile">Profil</Link>
<button onClick={handleLogout}>Logga ut</button>
</>
) : (
<>
<Link to="/login">Logga in</Link>
<Link to="/register">Registrera</Link>
</>
)}
</nav>
);
}
// Main App with Router
function App() {
return (
<AuthProvider>
<BrowserRouter>
<Navigation />
<Routes>
{/* Public routes */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* Protected routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
{/* Dynamic routes */}
<Route path="/users/:id" element={<UserProfile />} />
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
// Component med URL parameters
function UserProfile() {
const { id } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(id).then(setUser);
}, [id]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
React Router hooks:
useNavigate()
- programmatisk navigationuseParams()
- hämta URL-parametraruseLocation()
- få information om current location
Fråga 8: Custom Hooks och API Integration
Intervjuare: "Skapa en custom hook för API-anrop med loading och error states."
Bra svar:
// Custom hook för API calls
function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const refetch = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
refetch();
}, [refetch]);
return { data, loading, error, refetch };
}
// Custom hook för forms
function useForm(initialValues, validationRules = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = (name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// Validera när användaren ändrar värde
if (touched[name] && validationRules[name]) {
const error = validationRules[name](value);
setErrors(prev => ({ ...prev, [name]: error }));
}
};
const handleBlur = (name) => {
setTouched(prev => ({ ...prev, [name]: true }));
// Validera när fält lämnas
if (validationRules[name]) {
const error = validationRules[name](values[name]);
setErrors(prev => ({ ...prev, [name]: error }));
}
};
const isValid = Object.values(errors).every(error => !error);
return {
values,
errors,
touched,
handleChange,
handleBlur,
isValid,
setValues,
setErrors
};
}
// Användning av custom hooks
function UsersList() {
const { data: users, loading, error, refetch } = useApi('/api/users');
const {
values,
errors,
handleChange,
handleBlur,
isValid
} = useForm(
{ name: '', email: '' },
{
name: (value) => !value ? 'Namn krävs' : '',
email: (value) => !/\S+@\S+\.\S+/.test(value) ? 'Ogiltig e-post' : ''
}
);
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Users</h2>
<button onClick={refetch}>Refresh</button>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
<form>
<input
type="text"
placeholder="Name"
value={values.name}
onChange={(e) => handleChange('name', e.target.value)}
onBlur={() => handleBlur('name')}
/>
{errors.name && <span>{errors.name}</span>}
<input
type="email"
placeholder="Email"
value={values.email}
onChange={(e) => handleChange('email', e.target.value)}
onBlur={() => handleBlur('email')}
/>
{errors.email && <span>{errors.email}</span>}
<button disabled={!isValid}>Add User</button>
</form>
</div>
);
}
Fördelar med custom hooks:
- Återanvändbar logik mellan komponenter
- Separation of concerns
- Lättare testning
- Renare komponentkod
Fråga 9: Performance Optimization
Intervjuare: "Vilka tekniker använder du för att optimera React-applikationer?"
Bra svar:
import { memo, useMemo, useCallback, lazy, Suspense } from 'react';
// 1. React.memo för att förhindra onödiga re-renders
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
console.log('ExpensiveComponent rendered');
return (
<div>
<h3>{data.title}</h3>
<button onClick={onClick}>Click me</button>
</div>
);
});
// 2. useMemo för dyra beräkningar
function ProductList({ products, searchTerm, sortBy }) {
const filteredAndSortedProducts = useMemo(() => {
console.log('Calculating filtered products...');
let result = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
result.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
if (sortBy === 'name') return a.name.localeCompare(b.name);
return 0;
});
return result;
}, [products, searchTerm, sortBy]); // Bara omberäkna när dependencies ändras
return (
<div>
{filteredAndSortedProducts.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
// 3. useCallback för att stabilisera funktionsreferenser
function TodoApp() {
const [todos, setTodos] = useState([]);
// Utan useCallback skapas en ny funktion vid varje render
const handleToggleTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []); // Tom dependency array eftersom vi använder functional update
const handleDeleteTodo = useCallback((id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
}, []);
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggleTodo}
onDelete={handleDeleteTodo}
/>
))}
</div>
);
}
// 4. Lazy loading med React.lazy
const LazyDashboard = lazy(() => import('./Dashboard'));
const LazyProfile = lazy(() => import('./Profile'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<LazyDashboard />} />
<Route path="/profile" element={<LazyProfile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 5. Virtualisering för stora listor
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<div>{items[index].name}</div>
</div>
);
return (
<List
height={600} // Höjd på container
itemCount={items.length}
itemSize={50} // Höjd per item
>
{Row}
</List>
);
}
Performance tips:
- Använd React DevTools Profiler
- Minimera re-renders med memo/useMemo/useCallback
- Code splitting med lazy loading
- Virtualisering för stora listor
- Optimera bundle size
- Använd production builds
Fråga 10: Error Boundaries och Error Handling
Intervjuare: "Implementera error boundaries och robustfehantering i React."
Bra svar:
// Error Boundary Class Component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Uppdatera state så nästa render visar fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Logga felet till error reporting service
console.error('Error caught by boundary:', error, errorInfo);
this.setState({
error: error,
errorInfo: errorInfo
});
// Skicka till error monitoring (t.ex. Sentry)
// errorReportingService.captureException(error);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Något gick fel</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button onClick={() => window.location.reload()}>
Ladda om sidan
</button>
</div>
);
}
return this.props.children;
}
}
// Custom hook för async error handling
function useAsyncError() {
const [, setError] = useState();
return useCallback((error) => {
setError(() => {
throw error;
});
}, []);
}
// Component med error handling
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const throwAsyncError = useAsyncError();
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
const userData = await response.json();
setUser(userData);
} catch (error) {
// Kasta error till Error Boundary
throwAsyncError(error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId, throwAsyncError]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// App med Error Boundaries
function App() {
return (
<ErrorBoundary>
<Router>
<Header />
<ErrorBoundary>
<Routes>
<Route path="/user/:id" element={<UserProfile />} />
<Route path="/dashboard" element={
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
} />
</Routes>
</ErrorBoundary>
</Router>
</ErrorBoundary>
);
}
Error handling best practices:
- Använd Error Boundaries för att fånga rendering errors
- Implementera graceful degradation
- Logga fel till monitoring services
- Visa användarvänliga felmeddelanden
- Ha fallback UI för kritiska komponenter
Sammanfattning
Dessa frågor täcker de viktigaste aspekterna av React-utveckling:
- React Fundamentals: JSX, Virtual DOM, komponenter
- Hooks: useState, useEffect, custom hooks
- State Management: Props, Context API, state lifting
- Routing: React Router, navigation, protected routes
- Performance: Memo, useMemo, useCallback, lazy loading
- Error Handling: Error boundaries, async errors
- Best Practices: Forms, API integration, testing
Förberedelse-tips:
- Bygg små React-projekt för att öva praktiskt
- Förstå React lifecycle och när olika hooks körs
- Öva på performance-optimering med React DevTools
- Lär dig skillnaden mellan controlled/uncontrolled components
- Förstå när och varför du skulle använda Context vs props