Aller au contenu principal

Composants React/GraphQL moderne

Architecture front-end moderne avec React, Redux et build tools.

Vue d'ensemble

La plateforme APS intègre progressivement une architecture front-end moderne basée sur React et des outils de build modernes (Webpack), tout en maintenant la compatibilité avec le code historique basé sur jQuery et Knockout.

Cette approche hybride permet une migration progressive vers des technologies modernes sans réécriture complète de l'application.

Stack technique

Technologies front-end

Bibliothèques principales :

  • React 17 : Bibliothèque UI déclarative
  • React DOM 17 : Rendu React dans le DOM
  • React Redux 7.2 : Gestion d'état avec Redux
  • Redux Saga 1.1 : Gestion des effets de bord asynchrones
  • Redux Toolkit 1.7 : Outils Redux modernes

Bibliothèques de style :

  • styled-components 5.3 : CSS-in-JS
  • polished 4.1 : Utilitaires de style
  • classnames 2.3 : Gestion conditionnelle des classes CSS

Utilitaires :

  • prop-types 15.8 : Validation des props React
  • use-debounce 7.0 : Hook de debounce
  • react-content-loader 6.2 : Skeletons de chargement

Technologies historiques (legacy)

L'application utilise encore :

  • jQuery 3.6 : Manipulation DOM et AJAX
  • Knockout 3.5 : Data-binding MVVM
  • jQuery UI 1.12 : Composants UI
  • SignalR 2.4 : Communication temps réel
  • DevExpress : Contrôles grilles et composants avancés

Build tools

Webpack 5 :

  • Bundling des modules JavaScript/React
  • Minification et optimisation
  • Source maps pour debugging

Babel 7 :

  • Transpilation ES6+ → ES5
  • Support JSX (React)
  • Polyfills pour compatibilité navigateurs

Gulp 4 :

  • Compilation SASS → CSS
  • Concaténation de fichiers
  • Watch mode pour développement

TypeScript 4.7 :

  • Scripts de build typés
  • Outils d'administration

Architecture React

Organisation des composants

Les composants React sont organisés dans :

Web/Model/Scripts/form/
├── src/
│ ├── components/ # Composants React réutilisables
│ ├── containers/ # Composants connectés à Redux
│ ├── store/ # Configuration Redux
│ ├── sagas/ # Redux Sagas
│ └── utils/ # Utilitaires
└── webpack.config.js # Configuration Webpack

Pattern Redux

L'état de l'application est géré avec Redux via Redux Toolkit :

Store :

import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();

export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(sagaMiddleware),
});

sagaMiddleware.run(rootSaga);

Slices Redux Toolkit :

import { createSlice } from '@reduxjs/toolkit';

const documentSlice = createSlice({
name: 'document',
initialState: {
currentDocument: null,
loading: false,
error: null,
},
reducers: {
loadDocument: (state, action) => {
state.loading = true;
},
loadDocumentSuccess: (state, action) => {
state.currentDocument = action.payload;
state.loading = false;
},
loadDocumentFailure: (state, action) => {
state.error = action.payload;
state.loading = false;
},
},
});

export const { loadDocument, loadDocumentSuccess, loadDocumentFailure }
= documentSlice.actions;
export default documentSlice.reducer;

Redux Saga pour effets asynchrones

Les opérations asynchrones (appels API) sont gérées via Redux Saga :

import { call, put, takeLatest } from 'redux-saga/effects';
import { loadDocument, loadDocumentSuccess, loadDocumentFailure } from './documentSlice';

function* fetchDocumentSaga(action) {
try {
const response = yield call(fetch, `/api/documents/${action.payload}`);
const data = yield call([response, 'json']);
yield put(loadDocumentSuccess(data));
} catch (error) {
yield put(loadDocumentFailure(error.message));
}
}

export function* watchDocuments() {
yield takeLatest(loadDocument.type, fetchDocumentSaga);
}

Styled Components

Les styles sont définis avec styled-components pour encapsulation :

import styled from 'styled-components';
import { lighten } from 'polished';

export const Button = styled.button`
background-color: ${props => props.theme.primary};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;

&:hover {
background-color: ${props => lighten(0.1, props.theme.primary)};
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;

Build et Webpack

Configuration Webpack

Le fichier webpack.config.js configure la compilation :

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
entry: {
form: ['react-app-polyfill/ie11', 'react-app-polyfill/stable', './src/index.jsx'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
optimization: {
minimizer: [new TerserPlugin()],
},
resolve: {
extensions: ['.js', '.jsx'],
},
};

Scripts npm

Les scripts de build sont définis dans package.json :

{
"scripts": {
"webpack": "cross-env NODE_ENV=production webpack",
"webpack:watch": "cross-env NODE_ENV=development webpack watch",
"gulp": "gulp",
"gulp:watch": "gulp watch",
"build": "npm run gulp && npm run webpack"
}
}

Utilisation :

# Build production
npm run webpack

# Mode watch (développement)
npm run webpack:watch

# Build complet (CSS + JS)
npm run build

Intégration avec ASP.NET

Chargement des bundles

Les bundles Webpack sont chargés dans les vues ASP.NET :

@* Page ASP.NET MVC *@
<div id="react-root"></div>

<script src="~/Scripts/form/dist/form.bundle.js"></script>
<script>
// Initialisation React
FormApp.init({
documentId: '@Model.DocumentId',
formId: '@Model.FormId'
});
</script>

Communication JavaScript ↔ C#

Les données sont échangées via :

Données initiales (C# → JS) :

<script>
window.__INITIAL_STATE__ = @Html.Raw(Json.Encode(Model));
</script>

Appels API (JS → C#) :

// Appel à un endpoint ASP.NET
const response = await fetch('/api/documents/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(documentData),
});

Polyfills et compatibilité

Support IE11

L'application utilise des polyfills pour supporter Internet Explorer 11 :

import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';
import 'core-js/stable';

Packages de polyfills :

  • react-app-polyfill : Polyfills React pour IE11
  • core-js : Polyfills ES6+
  • @babel/polyfill : Polyfills Babel

Approche hybride

Migration progressive

L'architecture permet une coexistence de :

Code React moderne :

  • Nouveaux composants en React
  • Gestion d'état avec Redux
  • Styled components

Code legacy :

  • Composants jQuery existants
  • Data-binding Knockout
  • Contrôles DevExpress

Interopérabilité

Les composants React peuvent interagir avec le code legacy :

// Composant React appelant du code jQuery
import { useEffect } from 'react';

function LegacyComponent({ elementId }) {
useEffect(() => {
// Initialisation d'un composant jQuery
$(`#${elementId}`).datepicker({
dateFormat: 'dd/mm/yy'
});

return () => {
// Cleanup
$(`#${elementId}`).datepicker('destroy');
};
}, [elementId]);

return <div id={elementId}></div>;
}

Outils de développement

ESLint

Configuration ESLint pour qualité du code :

{
"extends": [
"airbnb",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:redux-saga/recommended",
"prettier"
],
"plugins": ["react", "react-hooks", "redux-saga"],
"rules": {
"react/prop-types": "warn",
"react-hooks/rules-of-hooks": "error",
"redux-saga/no-unhandled-errors": "error"
}
}

Prettier

Formatage automatique du code :

{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true
}

Jest

Tests unitaires JavaScript :

{
"scripts": {
"test:js": "jest"
}
}

GraphQL (Future)

⚠️ Note : GraphQL n'est pas encore implémenté dans l'architecture actuelle, mais pourrait être ajouté à l'avenir pour remplacer les API REST.

Avantages potentiels :

  • Requêtes flexibles côté client
  • Typage fort avec schemas GraphQL
  • Réduction du over-fetching
  • Introspection automatique

Stack envisagée :

  • Apollo Client : Client GraphQL React
  • HotChocolate : Serveur GraphQL .NET
  • GraphQL Code Generator : Génération TypeScript

Bonnes pratiques

Organisation des composants

Composants fonctionnels avec hooks :

import { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

function DocumentEditor({ documentId }) {
const dispatch = useDispatch();
const document = useSelector(state => state.documents.current);
const [isDirty, setIsDirty] = useState(false);

useEffect(() => {
dispatch(loadDocument(documentId));
}, [documentId, dispatch]);

// ...
}

Performance

Mémoïsation :

import { useMemo, useCallback } from 'react';

function ExpensiveComponent({ items }) {
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);

const handleClick = useCallback(
(id) => console.log('Clicked:', id),
[]
);

// ...
}

Code splitting :

import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
return (
<Suspense fallback={<div>Chargement...</div>}>
<HeavyComponent />
</Suspense>
);
}

Évolution future

Roadmap technique

Court terme :

  • Migration composants jQuery → React
  • Tests unitaires composants React
  • Optimisation bundles Webpack

Moyen terme :

  • Adoption TypeScript pour React
  • Introduction GraphQL
  • Migration vers React 18

Long terme :

  • Application 100% React
  • Server-Side Rendering (SSR)
  • Microfrontends

Références