DEV INDUSTRY Kaj Białas

Rewolucja React Hooks

Powrót do artykułów
React

Rewolucja React Hooks

React przeszedł niezwykłą transformację od swojego wprowadzenia w 2013 roku. Jako osoba, która pracowała z React przez jego główne fazy ewolucji, byłem świadkiem tego, jak wprowadzenie Hooks w React 16.8 fundamentalnie zmieniło sposób budowania interfejsów użytkownika. Ten artykuł eksploruje praktyczne implikacje tej ewolucji i dostarcza insights dla nowoczesnego rozwoju w React.

Dziedzictwo: Era komponentów klasowych

Przed Hooks, rozwój React charakteryzował się wyraźnym rozróżnieniem między stanowymi komponentami klasowymi a bezstanowymi komponentami funkcyjnymi. Ta separacja tworzyła przewidywalne wzorce, ale wprowadzała również złożoność.

Klasyczna architektura komponentów klasowych

1import React, { Component } from 'react';
2
3class UserProfile extends Component {
4  constructor(props) {
5    super(props);
6    this.state = {
7      user: null,
8      loading: true,
9      error: null
10    };
11  }
12
13  async componentDidMount() {
14    try {
15      const response = await fetch(`/api/users/${this.props.userId}`);
16      const user = await response.json();
17      this.setState({ user, loading: false });
18    } catch (error) {
19      this.setState({ error: error.message, loading: false });
20    }
21  }
22
23  componentDidUpdate(prevProps) {
24    if (prevProps.userId !== this.props.userId) {
25      this.setState({ loading: true });
26      this.fetchUser();
27    }
28  }
29
30  componentWillUnmount() {
31    // Cleanup subscriptions, cancel requests, etc.
32    this.abortController?.abort();
33  }
34
35  render() {
36    const { user, loading, error } = this.state;
37    
38    if (loading) return <div>Loading...</div>;
39    if (error) return <div>Error: {error}</div>;
40    
41    return (
42      <div className="user-profile">
43        <h1>{user.name}</h1>
44        <p>{user.email}</p>
45      </div>
46    );
47  }
48}
49
50export default UserProfile;

Wzorzec Container/Presentational

Ta era spopularyzowała separację odpowiedzialności przez komponenty Container i Presentational:

1// Container Component - obsługuje logikę i stan
2class UserProfileContainer extends Component {
3  state = {
4    user: null,
5    loading: true
6  };
7
8  componentDidMount() {
9    this.fetchUser();
10  }
11
12  fetchUser = async () => {
13    const user = await userService.getUser(this.props.userId);
14    this.setState({ user, loading: false });
15  };
16
17  render() {
18    return (
19      <UserProfilePresentation 
20        user={this.state.user}
21        loading={this.state.loading}
22        onRefresh={this.fetchUser}
23      />
24    );
25  }
26}
27
28// Presentational Component - czyste renderowanie
29const UserProfilePresentation = ({ user, loading, onRefresh }) => {
30  if (loading) return <Spinner />;
31  
32  return (
33    <div className="user-profile">
34      <h1>{user.name}</h1>
35      <button onClick={onRefresh}>Refresh</button>
36    </div>
37  );
38};

Rewolucja: Era Hooks

Wprowadzenie Hooks przekształciło rozwój React, umożliwiając komponentom funkcyjnym zarządzanie stanem i efektami ubocznymi, eliminując potrzebę komponentów klasowych w większości scenariuszy.

Nowoczesny komponent funkcyjny

1import React, { useState, useEffect } from 'react';
2
3const UserProfile = ({ userId }) => {
4  const [user, setUser] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  useEffect(() => {
9    const abortController = new AbortController();
10    
11    const fetchUser = async () => {
12      try {
13        setLoading(true);
14        const response = await fetch(`/api/users/${userId}`, {
15          signal: abortController.signal
16        });
17        const userData = await response.json();
18        setUser(userData);
19        setError(null);
20      } catch (err) {
21        if (err.name !== 'AbortError') {
22          setError(err.message);
23        }
24      } finally {
25        setLoading(false);
26      }
27    };
28
29    fetchUser();
30
31    return () => {
32      abortController.abort();
33    };
34  }, [userId]);
35
36  if (loading) return <div>Loading...</div>;
37  if (error) return <div>Error: {error}</div>;
38
39  return (
40    <div className="user-profile">
41      <h1>{user.name}</h1>
42      <p>{user.email}</p>
43    </div>
44  );
45};
46
47export default UserProfile;

Custom Hooks: Rewolucja logiki wielokrotnego użytku

Jedną z najpotężniejszych funkcji Hooks jest możliwość wyodrębniania logiki komponentów do wielokrotnego użytku custom hooks.

Tworzenie Custom Hook do pobierania danych

1import { useState, useEffect, useCallback } from 'react';
2
3const useApi = (url, dependencies = []) => {
4  const [data, setData] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  const fetchData = useCallback(async () => {
9    try {
10      setLoading(true);
11      setError(null);
12      
13      const response = await fetch(url);
14      if (!response.ok) {
15        throw new Error(`HTTP error! status: ${response.status}`);
16      }
17      
18      const result = await response.json();
19      setData(result);
20    } catch (err) {
21      setError(err.message);
22    } finally {
23      setLoading(false);
24    }
25  }, [url]);
26
27  useEffect(() => {
28    fetchData();
29  }, [fetchData, ...dependencies]);
30
31  const refetch = useCallback(() => {
32    fetchData();
33  }, [fetchData]);
34
35  return { data, loading, error, refetch };
36};
37
38// Użycie w komponencie
39const UserProfile = ({ userId }) => {
40  const { data: user, loading, error, refetch } = useApi(
41    `/api/users/${userId}`,
42    [userId]
43  );
44
45  if (loading) return <div>Loading...</div>;
46  if (error) return <div>Error: {error}</div>;
47
48  return (
49    <div className="user-profile">
50      <h1>{user.name}</h1>
51      <p>{user.email}</p>
52      <button onClick={refetch}>Refresh</button>
53    </div>
54  );
55};

Optymalizacja wydajności z Hooks

Nowoczesny React dostarcza potężnych narzędzi optymalizacji, które były złożone lub niemożliwe z komponentami klasowymi.

useMemo i useCallback

1import React, { useState, useMemo, useCallback } from 'react';
2
3const ExpensiveComponent = ({ items, filter, onItemClick }) => {
4  // Memoize kosztownych kalkulacji
5  const filteredItems = useMemo(() => {
6    return items.filter(item => 
7      item.name.toLowerCase().includes(filter.toLowerCase())
8    ).sort((a, b) => a.priority - b.priority);
9  }, [items, filter]);
10
11  // Memoize event handlers
12  const handleItemClick = useCallback((itemId) => {
13    onItemClick(itemId);
14  }, [onItemClick]);
15
16  const expensiveValue = useMemo(() => {
17    return filteredItems.reduce((acc, item) => acc + item.value, 0);
18  }, [filteredItems]);
19
20  return (
21    <div>
22      <div>Total Value: {expensiveValue}</div>
23      {filteredItems.map(item => (
24        <ItemComponent
25          key={item.id}
26          item={item}
27          onClick={handleItemClick}
28        />
29      ))}
30    </div>
31  );
32};
33
34const ItemComponent = React.memo(({ item, onClick }) => {
35  return (
36    <div 
37      className="item"
38      onClick={() => onClick(item.id)}
39    >
40      <h3>{item.name}</h3>
41      <p>{item.description}</p>
42    </div>
43  );
44});

Ewolucja Context API

Kombinacja Context API i Hooks dostarcza potężną alternatywę dla zewnętrznych bibliotek zarządzania stanem.

Nowoczesny wzorzec Context

1import React, { createContext, useContext, useReducer, useMemo } from 'react';
2
3const AuthContext = createContext();
4
5const authReducer = (state, action) => {
6  switch (action.type) {
7    case 'LOGIN_START':
8      return { ...state, loading: true, error: null };
9    
10    case 'LOGIN_SUCCESS':
11      return { 
12        ...state, 
13        loading: false, 
14        user: action.payload, 
15        isAuthenticated: true 
16      };
17    
18    case 'LOGIN_ERROR':
19      return { 
20        ...state, 
21        loading: false, 
22        error: action.payload, 
23        isAuthenticated: false 
24      };
25    
26    case 'LOGOUT':
27      return { 
28        ...state, 
29        user: null, 
30        isAuthenticated: false 
31      };
32    
33    default:
34      return state;
35  }
36};
37
38export const AuthProvider = ({ children }) => {
39  const [state, dispatch] = useReducer(authReducer, {
40    user: null,
41    isAuthenticated: false,
42    loading: false,
43    error: null
44  });
45
46  const login = async (credentials) => {
47    dispatch({ type: 'LOGIN_START' });
48    try {
49      const response = await authService.login(credentials);
50      dispatch({ type: 'LOGIN_SUCCESS', payload: response.user });
51    } catch (error) {
52      dispatch({ type: 'LOGIN_ERROR', payload: error.message });
53    }
54  };
55
56  const logout = () => {
57    authService.logout();
58    dispatch({ type: 'LOGOUT' });
59  };
60
61  const value = useMemo(() => ({
62    ...state,
63    login,
64    logout
65  }), [state]);
66
67  return (
68    <AuthContext.Provider value={value}>
69      {children}
70    </AuthContext.Provider>
71  );
72};
73
74export const useAuth = () => {
75  const context = useContext(AuthContext);
76  if (!context) {
77    throw new Error('useAuth must be used within an AuthProvider');
78  }
79  return context;
80};

Kluczowe wnioski

Ewolucja od komponentów klasowych do Hooks reprezentuje więcej niż zmianę składniową—to fundamentalna zmiana w sposobie myślenia o architekturze aplikacji React:

  • Uproszczony model mentalny: Hooks zapewniają bardziej bezpośredni sposób pracy z funkcjami React
  • Lepsze ponowne użycie kodu: Custom hooks umożliwiają dzielenie logiki bez złożoności wrapperów komponentów
  • Ulepszona wydajność: Zarówno pod względem rozmiaru bundle'a, jak i charakterystyk runtime
  • Wzbogacone doświadczenie developera: Łatwiejsze testowanie, debugowanie i organizacja kodu

Dla zespołów nadal używających komponentów klasowych, polecam stopniowe podejście do migracji, zaczynając od nowych komponentów i stopniowo aktualizując istniejące, gdy są potrzebne modyfikacje.

O mnie

O mnie

Tech Leader i Front-end Developer z 10-letnim doświadczeniem, kładący szczególny nacisk na architekturę aplikacji, zrozumienie domen biznesowych i testowalność tworzonych rozwiązań.

Jako trener i autor kursów, przykłada dużą wagę do aspektów jakościowych projektów oraz podnoszenia kompetencji i świadomości zespołu.

Entuzjasta podejścia Domain Driven Design, praktyk DevOps i oddany fan ekosystemu React.

Prywatnie relaksuje się przy dźwiękach mocnego rocka i szklance dobrej whisky.

01 Front-end Development
02 Solution Architecture
03 Unit Testing
04 Dev Ops
05 React
Zdjęcie Kaj Białas - Frontend Tech Lead