Pourquoi viser la programmation fonctionnelle avec React
Exploration des avantages et inconvénients de la programmation fonctionnelle et orientée objet, et des raisons du choix fonctionnel dans React
Exploration des avantages et inconvénients de la programmation fonctionnelle et orientée objet, et des raisons du choix fonctionnel dans React
Le mot de passe saisi sert à ouvrir, modifier et supprimer les commentaires secrets.
Lorsqu'on crée un flux de données basé sur des fonctions pures, la complexité peut parfois l'emporter sur les performances
| Catégorie | Frontend (React, etc.) | Backend (Node.js, Java, etc.) |
|---|---|---|
| Principaux éléments à considérer | Optimisation du rendu UI, suivi des changements d'état | Performance, gestion des ressources, flux transactionnel |
| Paradigme approprié | Programmation fonctionnelle | Programmation orientée objet |
| Avantages | Facilité de test, suivi des changements d'état avec immuabilité, réutilisabilité des composants | Structure basée sur les classes pour clarifier les responsabilités, maintien de l'état, conception centrée sur la connexion, facilité de structuration des logiques complexes |
| Inconvénients | Gênant pour des flux de calculs complexes, baisse de lisibilité avec une utilisation excessive des hooks | Difficulté à séparer l'état lors des tests, structure complexe pour optimiser le rerendu |
React s'est orienté vers la programmation fonctionnelle (Functional Programming, FP) ces dernières années. Il exploite activement les avantages de l'approche fonctionnelle, des composants UI simples jusqu'à la structure d'applications complexes. Cet article se propose de systématiser les raisons pour lesquelles React vise la programmation fonctionnelle et chacune de ses justifications.
Le cœur de la programmation fonctionnelle est la « fonction pure ». Une fonction pure est une fonction qui donne toujours le même résultat si elle reçoit les mêmes entrées et qui n'altère pas l'état externe.
Test d'un composant de type classe :
Composant fonctionnel :
Les composants fonctionnels permettent de séparer la logique et le rendu, ce qui permet de tester chacun de manière indépendante.
Exemple de fonction pure :
Dans la programmation fonctionnelle, les données sont traitées comme immuables.
Respecter l'immuabilité permet aux outils d'optimisation de React de fonctionner très efficacement :
Fonctionnement efficace de React.memo :
Détection précise des dépendances avec useMemo et useCallback :
Mauvaise méthode vs bonne méthode :
En respectant l'immuabilité, la détection des changements d'état peut se faire simplement par comparaison de référence (===).
Comparaison profonde vs comparaison de référence :
React utilise Object.is() (presque identique à ===) pour vérifier si l'état a changé :
En maintenant l'immuabilité comme cela, la détection des changements dans des structures de données complexes peut être effectuée en temps O(1), offrant ainsi des avantages de performance significatifs.
La mémoïsation (Memoization) est une technique d'optimisation qui retourne une valeur mise en cache pour éviter de recalculer des résultats pour des entrées identiques. La programmation fonctionnelle repose sur le concept de fonction pure (pure function), ce qui rend la mémoïsation très efficace.
Les fonctions non pures sont difficiles à mémoïser :
Ainsi, l'« immuabilité + pureté » de la fonction est en adéquation avec les principes de la mémoïsation, ce qui renforce l'efficacité des deux concepts.
En utilisant des composants fonctionnels et des hooks (useState, useReducer), le code de gestion d'état devient concis.
Cela permet de séparer clairement la logique de rendu de l'interface utilisateur de celle des données, améliorant ainsi la lisibilité.
La raison pour laquelle React adopte la programmation fonctionnelle est la clarté du code, la facilité des tests, l'optimisation et l'efficacité de la détection des changements.
Le paradigme fonctionnel est une stratégie efficace pour moduler les composants et contrôler la complexité.
Ceci est mon avis subjectif
Il semble que le frontend (React) soit adapté au paradigme fonctionnel pour rendre efficace le rendu de l'interface utilisateur, la détection des changements d'état, l'optimisation et les tests.
En revanche, je pense que pour le backend (Node.js, Java, etc.), face aux questions de performance, gestion des ressources et contrôle du flux de processus, l'orienté objet (OOP) est plus pratique.
class UserProfile extends React.Component {
state = { name: '', loading: false };
async componentDidMount() {
this.setState({ loading: true });
const user = await fetchUser(this.props.userId);
this.setState({ name: user.name, loading: false });
}
render() {
return <div>{this.state.loading ? 'Loading...' : this.state.name}</div>;
}
}
// Il faut considérer le cycle de vie et l'état lors des testsfunction UserProfile({ userId }) {
const { user, loading } = useUser(userId);
if (loading) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
// Seules les props doivent être vérifiées lors des tests (le Hook est testé séparément)function formatPrice(price, currency = 'USD') {
return `${currency} ${price.toFixed(2)}`;
}
// Prévisibilité du résultat uniquement par l'entrée
expect(formatPrice(10.5)).toBe('USD 10.50');
expect(formatPrice(20, 'KRW')).toBe('KRW 20.00');// Lorsque l'immuabilité est respectée
const TodoItem = React.memo(({ todo, onToggle }) => (
<div onClick={() => onToggle(todo.id)}>
{todo.text} {todo.completed ? '✓' : '○'}
</div>
));
// Si les props ont la même référence, pas de re-renderfunction TodoList({ todos, filter }) {
// La filtration ne se réexécute que lorsque le tableau todos est recréé
const filteredTodos = useMemo(() =>
todos.filter(todo => todo.category === filter),
[todos, filter]
);
// La fonction n'est pas recréée si les dépendances ne changent pas
const handleToggle = useCallback((id) =>
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)),
[]
);
return <div>{/* ... */}</div>;
}// ❌ Modification directe - les outils d'optimisation ne détectent pas les changements
function badUpdate() {
const newTodos = todos;
newTodos[0].completed = true;
setTodos(newTodos); // Même référence, pas de re-render
}
// ✅ Création d'un nouvel objet - les outils d'optimisation fonctionnent correctement
function goodUpdate() {
setTodos(prev => prev.map((todo, index) =>
index === 0 ? { ...todo, completed: true } : todo
));
}// ❌ Comparaison profonde - vérification récursive de toutes les propriétés (lent)
function deepEqual(obj1, obj2) {
// Il faut comparer toutes les clés et valeurs des objets
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
// ✅ Comparaison de référence - vérification uniquement de l'adresse mémoire (rapide)
function shallowEqual(obj1, obj2) {
return obj1 === obj2; // Une seule comparaison suffit
}function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: 'John', age: 25 });
// ✅ Les valeurs primitives garantissent automatiquement l'immuabilité
const increment = () => setCount(prev => prev + 1);
// ✅ Les objets doivent être recréés pour détecter les changements
const updateAge = () => setUser(prev => ({ ...prev, age: prev.age + 1 }));
// ❌ Ceci ne détectera pas le changement
const wrongUpdate = () => {
user.age += 1;
setUser(user); // Même référence, pas de re-render
};
}function DataTable({ items }) {
// Si les items ont la même référence, pas de nouveau tri
const sortedItems = useMemo(() => {
console.log('Tri en cours !'); // Permet de vérifier quand il est exécuté
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
return (
<table>
{sortedItems.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
))}
</table>
);
}useMemouseCallbackfunction ProductList({ products, searchTerm }) {
// Ne recommence pas à filtrer si searchTerm ou products ne changent pas
const filteredProducts = useMemo(() => {
console.log('Exécution du filtrage !');
return products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]);
return <div>{filteredProducts.map(/* ... */)}</div>;
}// ❌ Non pur - dépend de l'état externe
let discount = 0.1;
function calculatePrice(price) {
return price * (1 - discount); // Référence une variable externe
}
// ✅ Pur - toutes les entrées sont passées en paramètres
function calculatePrice(price, discount = 0) {
return price * (1 - discount);
}function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, completed: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
return (
<div>
<button onClick={() => dispatch({ type: 'ADD_TODO', text: 'New Todo' })}>
Add Todo
</button>
{/* ... */}
</div>
);
}function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Reutilisable dans plusieurs composants
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'ko');
return <div>{/* Interface des paramètres */}</div>;
}Le récit d'une aventure intense de 7 semaines : de l'amélioration de l'onboarding et du monitoring d'infrastructure à la communication temps réel et l'optimisation de personnages 3D.
Un compte rendu de l'apparition des modèles d'architecture UI (MVC · MVP · MVVM · Flux), avec leurs avantages et inconvénients, et une analyse comparative.

De l'idéation à l'implémentation du MVP en passant par les retours seniors : récit de la mise en place des fondations du projet et des défis techniques relevés
Une analyse de l'essence de la normalisation à travers la logique de mouvement dans les jeux et l'application de l'échelle dans Blender.
Retour sur les 6 semaines de sprint de groupe : défis d'architecture, dynamiques de collaboration et progression technique suite aux retours seniors.
Retour sur dix semaines d’apprentissage au Boostcamp : technologies étudiées, défis de conception, fatigue mentale et enseignements tirés de l’utilisation de l’IA.
Un retour sur la phase Challenge du Boostcamp : missions quotidiennes, apprentissage CS, feedback entre pairs, travail d’équipe et évolution de ma façon d’apprendre avec l’IA.
Exploration des avantages et inconvénients de la programmation fonctionnelle et orientée objet, et des raisons du choix fonctionnel dans React
Retour d’expérience sur la préparation, la phase Basic et le test de résolution de problèmes du Boostcamp Web·Mobile 10.
Un résumé de mon expérience de contribution au projet Githru pendant la phase Master de l’OSCCA.
Mon expérience durant la phase Challenge de l'OSCCA : propositions d’issues et première Pull Request sur le projet Githru.
Retour d’expérience sur ma candidature à l’Open Source Contribution Academy et mon exploration de l’extension Githru pour VSCode.
마지막 아티클까지 모두 확인했습니다.