Optimisation de la Gestion d'État avec React Context et TypeScript

Optimisation de la Gestion d'État avec React Context et TypeScript

27 Août 2023

Les contextes jouent un rôle central dans le développement React. Ils permettent de gérer efficacement l'état partagé et les données entre les composants, facilitant la communication transparente entre différentes parties de votre application. Comprendre comment exploiter la puissance des contextes est essentiel pour construire des applications React robustes et faciles à entretenir.


Qu'est-ce que les Contextes ?

Les contextes dans React sont un mécanisme pour transmettre efficacement des données à travers l'arbre des composants sans avoir à passer manuellement des props à chaque niveau. Imaginez-les comme un coffre global pour les données de votre application.

Pour illustrer cela avec un exemple quotidien, imaginez que vous planifiez un voyage en famille. Vos membres de famille représentent différents composants de votre application. Au lieu d'appeler individuellement chaque membre de la famille pour leur fournir les détails du voyage, vous pouvez utiliser un tableau blanc (contexte) dans votre salon où tout le monde peut voir les plans. Maintenant, si vous mettez à jour l'heure de départ sur le tableau blanc, tout le monde est immédiatement informé du changement.

Dans React, c'est similaire à la mise à jour des données dans un contexte, et tout composant qui s'abonne à ce contexte recevra automatiquement les informations mises à jour sans avoir besoin de communication directe entre les composants. Cela simplifie le partage de données et garantit la cohérence dans votre application, tout comme ce tableau blanc pratique pour la planification de votre voyage en famille.


Avantages

Les contextes offrent de nombreux avantages dans le développement React, ce qui en fait un outil indispensable pour la création d'applications évolutives et faciles à entretenir. Parmi les principaux avantages, citons :

  • Partage de données simplifié : Les contextes éliminent la nécessité de percer les props, facilitant le partage de données entre les composants à différents niveaux de l'arborescence des composants.
  • Code plus propre : Ils encouragent un code modulaire et propre en séparant les préoccupations de données des préoccupations de présentation, ce qui se traduit par des bases de code plus faciles à entretenir et à comprendre.
  • Gestion de l'état global : Les contextes excellent dans la gestion de l'état global, garantissant que les données essentielles de l'application restent cohérentes et accessibles dans toute votre application.
  • Amélioration des performances : En mettant à jour intelligemment uniquement les composants qui dépendent des données modifiées, les contextes contribuent à optimiser les performances en réduisant les rendus inutiles.
  • Lisibilité du code : L'utilisation de contextes pour la gestion de l'état améliore la lisibilité du code, facilitant la compréhension de la structure et du flux de votre application.

Intégrer des contextes dans vos projets React vous permet de créer des applications plus efficaces, faciles à entretenir et évolutives, ce qui se traduit finalement par une meilleure expérience de développement et une plus grande satisfaction des utilisateurs.


Cas pratique

Le défi

Imaginez que nous construisions une application météo qui affiche les conditions météorologiques actuelles pour différentes villes. Les données météorologiques de chaque ville comprennent son nom, sa température et sa description météorologique. Voici la structure de notre application :

Architecture du projet

Maintenant, si nous voulons récupérer des données dans SearchBar.tsx et les afficher dans CurrentWeather.container.tsx et WeekWeather.container.tsx sans utiliser de contexte, nous devons créer un état en haut de notre App.tsx :

1// App.tsx
2
3export type WeatherWeekData = {
4  temperature: number
5}
6
7export type WeatherData = {
8  town: string
9  current: WeatherWeekData
10  week: WeatherWeekData[]
11}
12
13function App() {
14  const [weatherData, setWeatherData] = useState<WeatherData | null>(null)
15
16  return (
17    <div className="App">
18      <Header />
19      <SearchBar setWeatherData={setWeatherData} />
20      {weatherData && <CurrentWeather town={weatherData.town} temperature={weatherData.current.temperature} />}
21      {weatherData && <WeekWeather town={weatherData.town} week={weatherData.week} />}
22      <Footer />
23    </div>
24  )
25}
26
27export default App
1// SearchBar.tsx
2
3type SearchBarProps = {
4  setWeatherData: (weatherData: WeatherData) => void
5}
6
7export const SearchBar = ({ setWeatherData }: SearchBarProps) => {
8  const [searchTerm, setSearchTerm] = useState<string>('')
9
10  const handleOnInputChange = (event: ChangeEvent<HTMLInputElement>) => {
11    // wait 1s delay before set the term and fetch data
12    setTimeout(() => {
13      setSearchTerm(event.target.value)
14    }, 1000)
15  }
16  const fetchData = (term: string) => {
17    fetch('https://mysuperAPI.com/search?term=' + term)
18      .then((response) => response.json())
19      .then((data) => setWeatherData(data))
20  }
21
22  useEffect(() => {
23    fetchData(searchTerm)
24  }, [searchTerm])
25
26  return (
27    <div>
28      <input placeholder="Find your town" value={searchTerm} onChange={handleOnInputChange} />
29    </div>
30  )
31}
1// CurrentWeather.container.tsx
2
3type CurrentWeatherProps = {
4  town: string
5  temperature: number
6}
7
8export const CurrentWeather = ({ town, temperature }: CurrentWeatherProps) => {
9  return (
10    <div>
11      <h1>Current Weather</h1>
12
13      <div>
14        <h2>{town}</h2>
15        <p>{temperature} °F</p>
16      </div>
17    </div>
18  )
19}
1// WeekWeather.container.tsx
2
3type WeekWeatherProps = {
4  town: string
5  week: WeatherWeekData[]
6}
7
8export const WeekWeather = ({ town, week }: WeekWeatherProps) => {
9  const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
10
11  return (
12    <div>
13      <h1>WeekWeather Weather</h1>
14
15      <div>
16        <h2>{town}</h2>
17
18        <div>
19          {week.map((day, index) => (
20            <div>
21              <h3>Day : {days[index]}</h3>
22              <p>{day.temperature} °F</p>
23            </div>
24          ))}
25        </div>
26      </div>
27    </div>
28  )
29}

Comme nous pouvons le voir, nous devons partager l'état (state) et la fonction setState pour nous assurer que les autres composants frères (sister components) disposent des données. Malheureusement, cette solution n'est pas maintenable. Les props sont dispersées partout, et les données ne sont pas vraiment centralisées.


Solution

Afin d'obtenir un code plus propre, nous allons créer un contexte ! Le but est de stocker les données de manière à ce qu'elles puissent être accessibles par tous les composants.

Structure : Créez un dossier src/contexts/ et créez 3 fichiers dans le dossier des contextes comme ceci :

Architecture des contextes

Explications

  • Actions de la météo : la liste de nos actions à l'exception de GET : setWeather, setFavoriteTown, et plus encore...
  • Fournisseur de météo : Cela entourera la partie de votre application qui nécessite un accès aux données. Il gère également l'état lui-même.
  • Réducteur météo : Il gère les différentes actions, donc si vous souhaitez modifier ou ajouter des données, c'est à lui de le faire.

Création de tous les fichiers :

Actions
1// weather.actions.ts
2
3import { WeatherData } from './weather.reducer'
4
5export enum EWeatherActions {
6  SET_WEATHER = 'SET_WEATHER'
7}
8
9type SetWeather = {
10  type: EWeatherActions.SET_WEATHER
11  payload: WeatherData
12}
13
14export const setWeather = (args: WeatherData): SetWeather => ({
15  type: EWeatherActions.SET_WEATHER,
16  payload: args
17})
18
19export type WeatherActions = SetWeather

Bon à savoir :

  • setWeather est l'action que nous devons appeler si nous voulons ajouter les données à notre contexte.
  • Nous exportons le type pour typer notre réducteur.
Reducer
1// weather.reducer.ts
2
3import { Reducer } from 'react'
4import { EWeatherActions, WeatherActions } from './weather.actions'
5
6export type WeatherWeekData = {
7  temperature: number
8}
9
10export type WeatherData = {
11  town: string
12  current: WeatherWeekData | null
13  week: WeatherWeekData[]
14}
15
16export type WeatherState = {
17  weather: WeatherData | null
18}
19
20export const initialState: WeatherState = {
21  weather: null
22}
23
24export const weatherReducer: Reducer<WeatherState, WeatherActions> = (state = initialState, action) => {
25  switch (action.type) {
26    case EWeatherActions.SET_WEATHER:
27      return {
28        ...state,
29        ...action.payload
30      }
31    default:
32      return { ...state }
33  }
34}

À noter :

  • Il y a beaucoup de types, mais les éléments importants sont initialState et weatherReducer.
  • initialState : Comme son nom l'indique, c'est l'état initial de notre contexte. Nous y plaçons simplement un objet météo avec nos données.
  • weatherReducer : C'est un commutateur / cas simple en fonction du type d'action.
Provider
1// weather.provider.ts
2
3import { createContext, Dispatch, ReactNode, useContext, useMemo, useReducer } from 'react'
4import { initialState, weatherReducer, WeatherState } from './weather.reducer'
5import { WeatherActions } from './weather.actions'
6
7type WeatherContext = WeatherState & {
8  dispatch: Dispatch<WeatherActions>
9}
10
11const weatherContext = createContext<WeatherContext>({ ...initialState, dispatch: () => {} })
12
13export const useWeatherContext = () => useContext(weatherContext)
14
15type WeatherProviderProps = {
16  children: ReactNode
17}
18
19export const WeatherProvider = ({ children }: WeatherProviderProps) => {
20  const [state, dispatch] = useReducer(weatherReducer, initialState)
21
22  const value: WeatherContext = useMemo(() => ({ ...state, dispatch }), [state])
23  return <weatherContext.Provider value={value}>{children}</weatherContext.Provider>
24}

À noter :

  • weathercontext : Variable non importante, elle sert uniquement à créer le WeatherProvider.
  • useWeatherContext : C'est un alias, un raccourci pour appeler notre 'useContext'.
  • WeatherProvider : Notre état, nous devons entourer la partie de notre application qui a besoin de données pour limiter l'accès et améliorer les performances.

Utilisons notre context!

Notre nouveau App.tsx:

1// App.tsx
2
3function App() {
4  return (
5    <div className="App">
6      <Header />
7      <WeatherProvider>
8        <SearchBar />
9        <CurrentWeather />
10        <WeekWeather />
11      </WeatherProvider>
12      <Footer />
13    </div>
14  )
15}
16
17export default App

Nous avons supprimé toutes les props et entouré la partie relative à la météo avec WeatherProvider pour partager les données.

Pour ajouter de la donneée :
1// SearchBar.tsx
2
3export const SearchBar = () => {
4  const { dispatch: dispatchWeather } = useWeatherContext()
5  const [searchTerm, setSearchTerm] = useState<string>('')
6
7  const handleOnInputChange = (event: ChangeEvent<HTMLInputElement>) => {
8    // wait 1s delay before set the term and fetch data
9    setTimeout(() => {
10      setSearchTerm(event.target.value)
11    }, 1000)
12  }
13  const fetchData = (term: string) => {
14    fetch('https://mysuperAPI.com/search?term=' + term)
15      .then((response) => response.json())
16      .then((data) => dispatchWeather(setWeather(data)))
17  }
18
19  useEffect(() => {
20    fetchData(searchTerm)
21  }, [searchTerm])
22
23  return (
24    <div>
25      <input placeholder="Find your town" value={searchTerm} onChange={handleOnInputChange} />
26    </div>
27  )
28}

Dans ce fichier, nous utilisons dispatch depuis useWeatherContext. Dispatch est une fonction qui vous permet d'utiliser l'une de nos actions définies. Ici, nous prenons le dispatch et le renommons en dispatchWeather. Le fait de renommer le dispatch facilite le débogage lorsque nous avons de nombreux contextes et dispatches.

Pour utiliser la donneée :
1// CurrentWeather.container.tsx
2
3export const CurrentWeather = () => {
4  const { weather } = useWeatherContext()
5
6  if (!weather) return <div>Please select a town.</div>
7
8  return (
9    <div>
10      <h1>Current Weather</h1>
11
12      <div>
13        <h2>{weather.town}</h2>
14        <p>{weather.current.temperature} °F</p>
15      </div>
16    </div>
17  )
18}
1// WeekWeather.container.tsx
2
3export const WeekWeather = () => {
4  const { weather } = useWeatherContext()
5  const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
6
7  if (!weather) return <div>Please select a town.</div>
8
9  return (
10    <div>
11      <h1>WeekWeather Weather</h1>
12
13      <div>
14        <h2>{weather.town}</h2>
15
16        <div>
17          {weather.week.map((day, index) => (
18            <div>
19              <h3>Day : {days[index]}</h3>
20              <p>{day.temperature} °F</p>
21            </div>
22          ))}
23        </div>
24      </div>
25    </div>
26  )
27}

Et voilà ! Nous avons créé et utilisé notre propre contexte propre ! Félicitations.


Aller plus loin

Une fois que vous aurez compris les bases des contextes React, vous pourrez pousser le développement de votre application au niveau supérieur en explorant des sujets avancés :

  • LocalStorage avec des contextes : Associez la puissance des contextes avec LocalStorage pour persister l'état de l'application. Cela est particulièrement utile pour conserver les préférences de l'utilisateur, telles que les choix de thème, les paramètres de l'utilisateur ou même le dernier état du panier d'achat d'un utilisateur. En liant les contextes avec LocalStorage, vous vous assurez que les données spécifiques à l'utilisateur sont conservées entre les sessions. Cela améliore l'expérience de l'utilisateur en offrant une continuité et une personnalisation.

  • Intégration avec Redux : Bien que les contextes React soient excellents pour gérer l'état local des composants, Redux est une bibliothèque de gestion de l'état robuste qui excelle dans la gestion de l'état global de toute votre application. Vous pouvez tirer parti des deux en utilisant Redux pour l'état global de l'application et les contextes pour la gestion de l'état au niveau des composants spécifiques. Cette approche hybride offre le meilleur des deux mondes, vous permettant de gérer et de partager efficacement des données entre les composants tout en conservant un magasin d'état global pour des données au niveau de l'application complexes.

  • Tests et débogage : Explorez les outils et les techniques pour tester et déboguer les applications qui utilisent des contextes. Des bibliothèques telles que React Testing Library et Redux DevTools peuvent être incroyablement précieuses pour garantir la fiabilité et les performances de votre code.


Conclusion

En résumé, les contextes React sont cruciaux pour le partage efficace de données dans vos applications. Ils simplifient le code, gèrent efficacement l'état global et améliorent les performances. Dans un exemple pratique, nous avons vu comment l'utilisation des contextes peut nettoyer considérablement votre code. En maîtrisant les contextes, vous construirez des applications plus efficaces et plus faciles à entretenir sans perdre l'intérêt de vos lecteurs.

Si vous avez apprécié ce tutoriel, envisagez de me suivre pour plus de contenus utiles. Votre soutien est grandement apprécié ! Merci !

X _brdnicolas

Voir plus d'articles