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 IE11core-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